diff --git a/README.md b/README.md index a529403..0475252 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,18 @@ * POST /v1/oauth2/token * POST /v1/payments/payment * GET /v1/payments/payment/%ID% - - GET /v1/payments/payment + * GET /v1/payments/payment + * GET /v1/payments/authorization/%ID% + * POST /v1/payments/authorization/%ID%/capture + * POST /v1/payments/authorization/%ID%/void + * POST /v1/payments/authorization/%ID%/reauthorize #### Create client +```go +import "github.com/logpacker/paypalsdk" +``` + ```go // Create a client instance c, err := paypalsdk.NewClient("clietnid", "secret", paypalsdk.APIBaseSandBox) @@ -40,6 +48,39 @@ paymentResult, err := c.CreateDirectPaypalPayment(amount, redirectURI, cancelURI // After approval user will be redirected to return_url from Request with PaymentID ``` +### Create any payment +```go +p := paypalsdk.Payment{ + Intent: "sale", + Payer: &paypalsdk.Payer{ + PaymentMethod: "credit_card", + FundingInstruments: []paypalsdk.FundingInstrument{paypalsdk.FundingInstrument{ + CreditCard: &paypalsdk.CreditCard{ + Number: "4111111111111111", + Type: "visa", + ExpireMonth: "11", + ExpireYear: "2020", + CVV2: "777", + FirstName: "John", + LastName: "Doe", + }, + }}, + }, + Transactions: []paypalsdk.Transaction{paypalsdk.Transaction{ + Amount: &paypalsdk.Amount{ + Currency: "USD", + Total: "200", + }, + Description: "My Payment", + }}, + RedirectURLs: &paypalsdk.RedirectURLs{ + ReturnURL: "http://...", + CancelURL: "http://...", + }, +} +paymentResponse, err := client.CreatePayment(p) +``` + #### Execute approved payment ```go @@ -64,3 +105,27 @@ payment, err := c.GetPayment(paymentID) // Get all payments slice payments, err := c.GetPayments() ``` + +#### Get authorization by ID + +```go +auth, err := c.GetAuthorization("AUTH-1") +``` + +#### Capture authorization + +```go +capture, err := c.CaptureAuthorization("AUTH-1", &paypalsdk.Amount{Total: "200", Currency: "USD"}, true) +``` + +#### Void authorization + +```go +auth, err := c.VoidAuthorization("AUTH-1") +``` + +#### Reauthorize authorization + +```go +auth, err := c.ReauthorizeAuthorization("AUTH-1", &paypalsdk.Amount{Total: "200", Currency: "USD"}) +``` diff --git a/auth.go b/auth.go index e037160..5df876c 100644 --- a/auth.go +++ b/auth.go @@ -27,3 +27,82 @@ func (c *Client) GetAccessToken() (*TokenResponse, error) { return &t, err } + +// GetAuthorization returns an authorization by ID +func (c *Client) GetAuthorization(authID string) (*Authorization, error) { + buf := bytes.NewBuffer([]byte("")) + req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID), buf) + if err != nil { + return &Authorization{}, err + } + + auth := &Authorization{} + + err = c.SendWithAuth(req, auth) + if err != nil { + return auth, err + } + + return auth, nil +} + +// CaptureAuthorization captures and process an existing authorization. +// To use this method, the original payment must have Intent set to "authorize" +func (c *Client) CaptureAuthorization(authID string, a *Amount, isFinalCapture bool) (*Capture, error) { + isFinalStr := "false" + if isFinalCapture { + isFinalStr = "true" + } + buf := bytes.NewBuffer([]byte("{\"amount\":{\"currency\":\"" + a.Currency + "\",\"total\":\"" + a.Total + "\"},\"is_final_capture\":" + isFinalStr + "}")) + req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/capture"), buf) + if err != nil { + return &Capture{}, err + } + + capture := &Capture{} + + err = c.SendWithAuth(req, capture) + if err != nil { + return capture, err + } + + return capture, nil +} + +// VoidAuthorization voids a previously authorized payment +func (c *Client) VoidAuthorization(authID string) (*Authorization, error) { + buf := bytes.NewBuffer([]byte("")) + req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/void"), buf) + if err != nil { + return &Authorization{}, err + } + + auth := &Authorization{} + + err = c.SendWithAuth(req, auth) + if err != nil { + return auth, err + } + + return auth, nil +} + +// ReauthorizeAuthorization reauthorize a Paypal account payment. +// PayPal recommends to reauthorize payment after ~3 days +func (c *Client) ReauthorizeAuthorization(authID string, a *Amount) (*Authorization, error) { + buf := bytes.NewBuffer([]byte("{\"amount\":{\"currency\":\"" + a.Currency + "\",\"total\":\"" + a.Total + "\"}}")) + req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/reauthorize"), buf) + if err != nil { + return &Authorization{}, err + } + + auth := &Authorization{} + + err = c.SendWithAuth(req, auth) + if err != nil { + return auth, err + } + + return auth, nil + +} diff --git a/auth_test.go b/auth_test.go index 8312433..2a853a1 100644 --- a/auth_test.go +++ b/auth_test.go @@ -8,3 +8,47 @@ func TestGetAccessToken(t *testing.T) { c, _ := NewClient("clid", "secret", APIBaseSandBox) c.GetAccessToken() } + +func TestGetAuthorization(t *testing.T) { + c, _ := NewClient("clid", "secret", APIBaseSandBox) + c.GetAccessToken() + + _, err := c.GetAuthorization("123") + + if err == nil { + t.Errorf("Error must be returned for invalid Auth ID") + } +} + +func TestCaptureAuthorization(t *testing.T) { + c, _ := NewClient("clid", "secret", APIBaseSandBox) + c.GetAccessToken() + + _, err := c.CaptureAuthorization("123", &Amount{Total: "200", Currency: "USD"}, true) + + if err == nil { + t.Errorf("Error must be returned for invalid Auth ID") + } +} + +func TestVoidAuthorization(t *testing.T) { + c, _ := NewClient("clid", "secret", APIBaseSandBox) + c.GetAccessToken() + + _, err := c.VoidAuthorization("123") + + if err == nil { + t.Errorf("Error must be returned for invalid Auth ID") + } +} + +func TestReauthorizeAuthorization(t *testing.T) { + c, _ := NewClient("clid", "secret", APIBaseSandBox) + c.GetAccessToken() + + _, err := c.ReauthorizeAuthorization("123", &Amount{Total: "200", Currency: "USD"}) + + if err == nil { + t.Errorf("Error must be returned for invalid Auth ID") + } +} diff --git a/client.go b/client.go index d184892..35259ab 100644 --- a/client.go +++ b/client.go @@ -1,6 +1,7 @@ package paypalsdk import ( + "bytes" "encoding/json" "errors" "io" @@ -92,11 +93,28 @@ func (c *Client) Send(req *http.Request, v interface{}) error { // If the access token soon to be expired, it will try to get a new one before // making the main request func (c *Client) SendWithAuth(req *http.Request, v interface{}) error { - req.Header.Set("Authorization", "Bearer "+c.Token.Token) + if c.Token != nil { + req.Header.Set("Authorization", "Bearer "+c.Token.Token) + } return c.Send(req, v) } +// NewRequest constructs a request +// Convert payload to a JSON +func (c *Client) NewRequest(method, url string, payload interface{}) (*http.Request, error) { + var buf io.Reader + if payload != nil { + var b []byte + b, err := json.Marshal(&payload) + if err != nil { + return nil, err + } + buf = bytes.NewBuffer(b) + } + return http.NewRequest(method, url, buf) +} + func (c *Client) log(req *http.Request, resp *http.Response) { if c.LogFile != "" { os.OpenFile(c.LogFile, os.O_CREATE, 0755) diff --git a/examples/main.go b/examples/main.go index 6dd9c78..642819a 100644 --- a/examples/main.go +++ b/examples/main.go @@ -35,5 +35,41 @@ func main() { fmt.Println("ERROR: " + err.Error()) os.Exit(1) } + + p := paypalsdk.Payment{ + Intent: "sale", + Payer: &paypalsdk.Payer{ + PaymentMethod: "credit_card", + FundingInstruments: []paypalsdk.FundingInstrument{paypalsdk.FundingInstrument{ + CreditCard: &paypalsdk.CreditCard{ + Number: "4111111111111111", + Type: "visa", + ExpireMonth: "11", + ExpireYear: "2020", + CVV2: "777", + FirstName: "John", + LastName: "Doe", + }, + }}, + }, + Transactions: []paypalsdk.Transaction{paypalsdk.Transaction{ + Amount: &paypalsdk.Amount{ + Currency: "USD", + Total: "200", + }, + Description: "My Payment", + }}, + RedirectURLs: &paypalsdk.RedirectURLs{ + ReturnURL: "http://...", + CancelURL: "http://...", + }, + } + paymentResponse, err := client.CreatePayment(p) + if err == nil { + fmt.Println("DEBUG: CreatedPaymentID=" + paymentResponse.Payment.ID) + } else { + fmt.Println("ERROR: " + err.Error()) + os.Exit(1) + } fmt.Println("OK") } diff --git a/payment.go b/payment.go index 81aeb30..a667244 100644 --- a/payment.go +++ b/payment.go @@ -12,7 +12,14 @@ type ListPaymentsResp struct { Payments []Payment `json:"payments"` } +// CreatePaymentResp returned by CreatePayment +type CreatePaymentResp struct { + *Payment + Links []Links `json:"links"` +} + // CreateDirectPaypalPayment sends request with payment +// CreatePayment is more common function for any kind of payment func (c *Client) CreateDirectPaypalPayment(amount Amount, redirectURI string, cancelURI string, description string) (*PaymentResponse, error) { buf := bytes.NewBuffer([]byte("{\"intent\":\"sale\",\"payer\":{\"payment_method\":\"paypal\"}," + "\"transactions\":[{\"amount\":{\"total\":\"" + amount.Total + @@ -39,6 +46,23 @@ func (c *Client) CreateDirectPaypalPayment(amount Amount, redirectURI string, ca return &p, err } +// CreatePayment creates a payment in Paypal +func (c *Client) CreatePayment(p Payment) (*CreatePaymentResp, error) { + req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment"), p) + if err != nil { + return &CreatePaymentResp{}, err + } + + response := &CreatePaymentResp{} + + err = c.SendWithAuth(req, response) + if err != nil { + return response, err + } + + return response, nil +} + // ExecuteApprovedPayment executes approved payment func (c *Client) ExecuteApprovedPayment(paymentID string, payerID string) (*ExecuteResponse, error) { buf := bytes.NewBuffer([]byte("{\"payer_id\":\"" + payerID + "\"}")) diff --git a/types.go b/types.go index a32eae9..a0529eb 100644 --- a/types.go +++ b/types.go @@ -25,6 +25,33 @@ type ( Token *TokenResponse } + // Authorization maps to the authorization object + Authorization struct { + Amount *Amount `json:"amount,omitempty"` + CreateTime *time.Time `json:"create_time,omitempty"` + UpdateTime *time.Time `json:"update_time,omitempty"` + State string `json:"state,omitempty"` + ParentPayment string `json:"parent_payment,omitempty"` + ID string `json:"id,omitempty"` + ValidUntil *time.Time `json:"valid_until,omitempty"` + Links []Links `json:"links,omitempty"` + ClearingTime string `json:"clearing_time,omitempty"` + ProtectionEligibility string `json:"protection_eligibility,omitempty"` + ProtectionEligibilityType string `json:"protection_eligibility_type,omitempty"` + } + + // Capture maps to the capture object + Capture struct { + Amount *Amount `json:"amount,omitempty"` + IsFinalCapture bool `json:"is_final_capture"` + CreateTime *time.Time `json:"create_time,omitempty"` + UpdateTime *time.Time `json:"update_time,omitempty"` + State string `json:"state,omitempty"` + ParentPayment string `json:"parent_payment,omitempty"` + ID string `json:"id,omitempty"` + Links []Links `json:"links,omitempty"` + } + // Address maps to address object Address struct { Line1 string `json:"line1"` @@ -135,6 +162,14 @@ type ( CancelURL string `json:"cancel_url,omitempty"` } + // Links maps to links object + Links struct { + Href string `json:"href"` + Rel string `json:"rel"` + Method string `json:"method"` + Enctype string `json:"enctype"` + } + // Payment maps to payment object Payment struct { Intent string `json:"intent"`