Compare commits

..

No commits in common. "master" and "fix-quantity-type" have entirely different histories.

36 changed files with 1780 additions and 3380 deletions

View File

@ -1,4 +0,0 @@
#### What does this PR do?
#### Where should the reviewer start?
#### How should this be manually tested?
#### Any background context you want to provide?

View File

@ -1,50 +0,0 @@
name: Lint and Test
on:
push:
branches:
- features/*
- testing/*
- fix/*
- enhance/*
pull_request:
branches:
- master
- staging
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
- name: Install dependencies
run: go get .
- name: Install linters
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
go install mvdan.cc/unparam@latest
- name: go vet
run: go vet ${{ inputs.path }}
- name: staticcheck
run: staticcheck ${{ inputs.path }}
- name: unparam
run: unparam ${{ inputs.path }}
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
- name: Install dependencies
run: go get .
- name: Run Tests
run: go test -v -race ./...

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
.idea
.vscode
vendor/

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: go
go:
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- 1.10
install:
- export PATH=$PATH:$HOME/gopath/bin
script:
- go test -v -race

View File

@ -1,10 +0,0 @@
First off all, thank you for considering contributing to this project. It's people like you that make it such a great tool.
Keep an open mind! Improving documentation, bug triaging, or writing tutorials are all examples of helpful contributions that mean less work for you.
Some basic suggestions to get you started:
- Make sure the PR is up-to-date with the latest changes in the main branch.
- Make sure the PR passes all the tests.
- Make sure the PR passes the linter.
- Make sure the PR is well documented and formatted.
- Make sure the PR is well tested.

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2024 Aliaksandr Pliutau
Copyright (c) 2017 Aliaksandr Pliutau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

279
README.md
View File

@ -1,31 +1,130 @@
[Docs](https://pkg.go.dev/github.com/plutov/paypal)
[![Go Report Card](https://goreportcard.com/badge/logpacker/PayPal-Go-SDK)](https://goreportcard.com/report/logpacker/PayPal-Go-SDK)
[![Build Status](https://travis-ci.org/logpacker/PayPal-Go-SDK.svg?branch=master)](https://travis-ci.org/logpacker/PayPal-Go-SDK)
[![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/logpacker/PayPal-Go-SDK)
[![Chat at https://gitter.im/logpacker/PayPal-Go-SDK](https://img.shields.io/badge/gitter-dev_chat-46bc99.svg)](https://gitter.im/logpacker/PayPal-Go-SDK?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Sourcegraph](https://sourcegraph.com/github.com/logpacker/PayPal-Go-SDK/-/badge.svg)](https://sourcegraph.com/github.com/logpacker/PayPal-Go-SDK?badge)
<p>
<a href="https://github.com/plutov/paypal/releases"><img src="https://img.shields.io/github/release/plutov/paypal.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/plutov/paypal?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
</p>
### GO client for PayPal REST API
# Go client for PayPal REST API
### Coverage
* POST /v1/oauth2/token
* POST /v1/payments/payment
* GET /v1/payments/payment/**ID**
* 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
* GET /v1/payments/sale/**ID**
* POST /v1/payments/sale/**ID**/refund
* GET /v1/payments/refund/**ID**
* GET /v1/payments/orders/**ID**
* POST /v1/payments/orders/**ID**/authorize
* POST /v1/payments/orders/**ID**/capture
* POST /v1/payments/orders/**ID**/do-void
* POST /v1/identity/openidconnect/tokenservice
* GET /v1/identity/openidconnect/userinfo/?schema=**SCHEMA**
* POST /v1/payments/payouts
* GET /v1/payments/payouts/**ID**
* GET /v1/payments/payouts-item/**ID**
* POST /v1/payments/payouts-item/**ID**/cancel
* GET /v1/payment-experience/web-profiles
* POST /v1/payment-experience/web-profiles
* GET /v1/payment-experience/web-profiles/**ID**
* PUT /v1/payment-experience/web-profiles/**ID**
* DELETE /v1/payment-experience/web-profiles/**ID**
* POST /v1/vault/credit-cards
* DELETE /v1/vault/credit-cards/**ID**
* PATCH /v1/vault/credit-cards/**ID**
* GET /v1/vault/credit-cards/**ID**
* GET /v1/vault/credit-cards
* POST /v1/payments/billing-plans
* PATCH /v1/payments/billing-plans/***ID***
* POST /v1/payments/billing-agreements
* POST /v1/payments/billing-agreements/***TOKEN***/agreement-execute
## Paypal REST API Docs
### Missing endpoints
It is possible that some endpoints are missing in this SDK Client, but you can use built-in **paypalsdk** functions to perform a request: **NewClient -> NewRequest -> SendWithAuth**
[Get started with PayPal REST APIs](https://developer.paypal.com/api/rest/)
## Missing endpoints
It is possible that some endpoints are missing in this client, but you can use built-in `paypal` functions to perform a request: `NewClient -> NewRequest -> SendWithAuth`
## Usage
### New Client
```go
import "github.com/plutov/paypal/v4"
import "github.com/logpacker/PayPal-Go-SDK"
// Create a client instance
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox)
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
c.SetLog(os.Stdout) // Set log to terminal stdout
accessToken, err := c.GetAccessToken()
```
## Get authorization by ID
### Create direct paypal payment
```go
amount := paypalsdk.Amount{
Total: "7.00",
Currency: "USD",
}
redirectURI := "http://example.com/redirect-uri"
cancelURI := "http://example.com/cancel-uri"
description := "Description for this payment"
paymentResult, err := c.CreateDirectPaypalPayment(amount, redirectURI, cancelURI, description)
```
### Create custom 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: "7.00",
},
Description: "My Payment",
}},
RedirectURLs: &paypalsdk.RedirectURLs{
ReturnURL: "http://...",
CancelURL: "http://...",
},
}
paymentResponse, err := client.CreatePayment(p)
```
### Execute approved payment
```go
paymentID := "PAY-17S8410768582940NKEE66EQ"
payerID := "7E7MGXCWTTKK2"
executeResult, err := c.ExecuteApprovedPayment(paymentID, payerID)
```
### Get payment by ID
```go
payment, err := c.GetPayment("PAY-17S8410768582940NKEE66EQ")
```
### Get list of payments
```go
payments, err := c.GetPayments()
```
### Get authorization by ID
```go
auth, err := c.GetAuthorization("2DC87612EK520411B")
@ -34,7 +133,7 @@ auth, err := c.GetAuthorization("2DC87612EK520411B")
### Capture authorization
```go
capture, err := c.CaptureAuthorization(authID, &paypal.Amount{Total: "7.00", Currency: "USD"}, true)
capture, err := c.CaptureAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true)
```
### Void authorization
@ -46,7 +145,22 @@ auth, err := c.VoidAuthorization(authID)
### Reauthorize authorization
```go
auth, err := c.ReauthorizeAuthorization(authID, &paypal.Amount{Total: "7.00", Currency: "USD"})
auth, err := c.ReauthorizeAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
```
### Get Sale by ID
```go
sale, err := c.GetSale("36C38912MN9658832")
```
### Refund Sale by ID
```go
// Full
refund, err := c.RefundSale(saleID, nil)
// Partial
refund, err := c.RefundSale(saleID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
```
### Get Refund by ID
@ -61,32 +175,22 @@ refund, err := c.GetRefund("O-4J082351X3132253H")
order, err := c.GetOrder("O-4J082351X3132253H")
```
### Create an Order
```go
ctx := context.Background()
units := []paypal.PurchaseUnitRequest{}
source := &paypal.PaymentSource{}
appCtx := &paypal.ApplicationContext{}
order, err := c.CreateOrder(ctx, paypal.OrderIntentCapture, units, ource, appCtx)
```
### Update Order by ID
```go
order, err := c.UpdateOrder("O-4J082351X3132253H", []paypal.PurchaseUnitRequest{})
```
### Authorize Order
```go
auth, err := c.AuthorizeOrder(orderID, paypal.AuthorizeOrderRequest{})
auth, err := c.AuthorizeOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
```
### Capture Order
```go
capture, err := c.CaptureOrder(orderID, paypal.CaptureOrderRequest{})
capture, err := c.CaptureOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true, nil)
```
### Void Order
```go
order, err := c.VoidOrder(orderID)
```
### Identity
@ -106,15 +210,15 @@ userInfo, err := c.GetUserInfo("openid")
### Create single payout to email
```go
payout := paypal.Payout{
SenderBatchHeader: &paypal.SenderBatchHeader{
payout := paypalsdk.Payout{
SenderBatchHeader: &paypalsdk.SenderBatchHeader{
EmailSubject: "Subject will be displayed on PayPal",
},
Items: []paypal.PayoutItem{
paypal.PayoutItem{
Items: []paypalsdk.PayoutItem{
paypalsdk.PayoutItem{
RecipientType: "EMAIL",
Receiver: "single-email-payout@mail.com",
Amount: &paypal.AmountPayout{
Amount: &paypalsdk.AmountPayout{
Value: "15.11",
Currency: "USD",
},
@ -124,7 +228,7 @@ payout := paypal.Payout{
},
}
payoutResp, err := c.CreatePayout(payout)
payoutResp, err := c.CreateSinglePayout(payout)
```
### Get payout by ID
@ -186,6 +290,7 @@ webprofiles, err := c.GetWebProfiles()
### Update web experience profile
```go
webprofile := WebProfile{
ID: "XP-CP6S-W9DY-96H8-MVN2",
Name: "Shop YeowZa! YeowZa! ",
@ -203,7 +308,7 @@ err := c.DeleteWebProfile("XP-CP6S-W9DY-96H8-MVN2")
```go
// Store CC
c.StoreCreditCard(paypal.CreditCard{
c.StoreCreditCard(paypalsdk.CreditCard{
Number: "4417119669820331",
Type: "visa",
ExpireMonth: "11",
@ -217,8 +322,8 @@ c.StoreCreditCard(paypal.CreditCard{
c.DeleteCreditCard("CARD-ID-123")
// Edit it
c.PatchCreditCard("CARD-ID-123", []paypal.CreditCardField{
paypal.CreditCardField{
c.PatchCreditCard("CARD-ID-123", []paypalsdk.CreditCardField{
paypalsdk.CreditCardField{
Operation: "replace",
Path: "/billing_address/line1",
Value: "New value",
@ -232,80 +337,14 @@ c.GetCreditCard("CARD-ID-123")
c.GetCreditCards(nil)
```
### Webhooks
### How to Contribute
```go
// Create a webhook
c.CreateWebhook(paypal.CreateWebhookRequest{
URL: "webhook URL",
EventTypes: []paypal.WebhookEventType{
paypal.WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
})
* Fork a repository
* Add/Fix something
* Check that tests are passing
* Create PR
// Update a registered webhook
c.UpdateWebhook("WebhookID", []paypal.WebhookField{
paypal.WebhookField{
Operation: "replace",
Path: "/event_types",
Value: []interface{}{
map[string]interface{}{
"name": "PAYMENT.SALE.REFUNDED",
},
},
},
})
### Tests
// Get a registered webhook
c.GetWebhook("WebhookID")
// Delete a webhook
c.DeleteWebhook("WebhookID")
// List registered webhooks
c.ListWebhooks(paypal.AncorTypeApplication)
```
### Generate Next Invoice Number
```go
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
c.GenerateInvoiceNumber(ctx) // might return something like "0001" or "0010".
```
### Get Invoice Details by ID
```go
// the second argument is an ID, it should be valid
invoice, err := c.GetInvoiceDetails(ctx, "INV2-XFXV-YW42-ZANU-4F33")
```
- for now, we are yet to implement the ShowAllInvoices endpoint, so use the following cURL request for the same(this gives you the list of invoice-IDs for this customer)
```bash
curl -v -X GET https://api-m.sandbox.paypal.com/v2/invoicing/invoices?total_required=true \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <Token>"
```
- refer to the beginning of this Usage section for obtaining a Token.
## How to Contribute
- Fork a repository
- Add/Fix something
- Check that tests are passing
- Create PR
Main contributors:
- [Alex Pliutau](https://github.com/plutov)
- [Roopak Venkatakrishnan](https://github.com/roopakv)
## Tests
```
go test -v ./...
```
* Unit tests: `go test`
* Integration tests: `go test -tags=integration`

View File

@ -1,84 +1,68 @@
package paypal
package paypalsdk
import (
"bytes"
"context"
"fmt"
"net/http"
"strconv"
)
// GetAuthorization returns an authorization by ID
// Endpoint: GET /v2/payments/authorizations/ID
func (c *Client) GetAuthorization(ctx context.Context, authID string) (*Authorization, error) {
// Endpoint: GET /v1/payments/authorization/ID
func (c *Client) GetAuthorization(authID string) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(""))
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/payments/authorizations/", authID), buf)
auth := &Authorization{}
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payments/authorization/", authID), buf)
if err != nil {
return auth, err
return &Authorization{}, err
}
auth := &Authorization{}
err = c.SendWithAuth(req, auth)
return auth, err
}
// CaptureAuthorization captures and process an existing authorization.
// To use this method, the original payment must have Intent set to "authorize"
// Endpoint: POST /v2/payments/authorizations/ID/capture
func (c *Client) CaptureAuthorization(ctx context.Context, authID string, paymentCaptureRequest *PaymentCaptureRequest) (*PaymentCaptureResponse, error) {
return c.CaptureAuthorizationWithPaypalRequestId(ctx, authID, paymentCaptureRequest, "")
}
// CaptureAuthorization captures and process an existing authorization with idempotency.
// To use this method, the original payment must have Intent set to "authorize"
// Endpoint: POST /v2/payments/authorizations/ID/capture
func (c *Client) CaptureAuthorizationWithPaypalRequestId(ctx context.Context,
authID string,
paymentCaptureRequest *PaymentCaptureRequest,
requestID string,
) (*PaymentCaptureResponse, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/capture"), paymentCaptureRequest)
paymentCaptureResponse := &PaymentCaptureResponse{}
// Endpoint: POST /v1/payments/authorization/ID/capture
func (c *Client) CaptureAuthorization(authID string, a *Amount, isFinalCapture bool) (*Capture, error) {
isFinalStr := strconv.FormatBool(isFinalCapture)
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 paymentCaptureResponse, err
return &Capture{}, err
}
if requestID != "" {
req.Header.Set("PayPal-Request-Id", requestID)
}
err = c.SendWithAuth(req, paymentCaptureResponse)
return paymentCaptureResponse, err
capture := &Capture{}
err = c.SendWithAuth(req, capture)
return capture, err
}
// VoidAuthorization voids a previously authorized payment
// Endpoint: POST /v2/payments/authorizations/ID/void
func (c *Client) VoidAuthorization(ctx context.Context, authID string) (*Authorization, error) {
// Endpoint: POST /v1/payments/authorization/ID/void
func (c *Client) VoidAuthorization(authID string) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(""))
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/void"), buf)
auth := &Authorization{}
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/void"), buf)
if err != nil {
return auth, err
return &Authorization{}, err
}
auth := &Authorization{}
err = c.SendWithAuth(req, auth)
return auth, err
}
// ReauthorizeAuthorization reauthorize a Paypal account payment.
// PayPal recommends reauthorizing payment after ~3 days
// Endpoint: POST /v2/payments/authorizations/ID/reauthorize
func (c *Client) ReauthorizeAuthorization(ctx context.Context, authID string, a *Amount) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(`{"amount":{"currency_code":"` + a.Currency + `","value":"` + a.Total + `"}}`))
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/reauthorize"), buf)
auth := &Authorization{}
// PayPal recommends to reauthorize payment after ~3 days
// Endpoint: POST /v1/payments/authorization/ID/reauthorize
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 auth, err
return &Authorization{}, err
}
auth := &Authorization{}
err = c.SendWithAuth(req, auth)
return auth, err
}

98
billing.go Normal file
View File

@ -0,0 +1,98 @@
package paypalsdk
import (
"bytes"
"errors"
"fmt"
"net/http"
"time"
)
type (
// CreateBillingResp struct
CreateBillingResp struct {
ID string `json:"id,omitempty"`
State string `json:"state,omitempty"`
PaymentDefinitions []PaymentDefinition `json:"payment_definitions,omitempty"`
MerchantPreferences MerchantPreferences `json:"merchant_preferences,omitempty"`
CreateTime time.Time `json:"create_time,omitempty"`
UpdateTime time.Time `json:"update_time,omitempty"`
Links []Link `json:"links,omitempty"`
}
// CreateAgreementResp struct
CreateAgreementResp struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Plan BillingPlan `json:"plan,omitempty"`
Links []Link `json:"links,omitempty"`
StartTime time.Time `json:"start_time,omitempty"`
}
)
// CreateBillingPlan creates a billing plan in Paypal
// Endpoint: POST /v1/payments/billing-plans
func (c *Client) CreateBillingPlan(plan BillingPlan) (*CreateBillingResp, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), plan)
response := &CreateBillingResp{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// ActivatePlan activates a billing plan
// By default, a new plan is not activated
// Endpoint: PATCH /v1/payments/billing-plans/
func (c *Client) ActivatePlan(planID string) error {
buf := bytes.NewBuffer([]byte("[{\"op\":\"replace\",\"path\":\"/\",\"value\":{\"state\":\"ACTIVE\"}}]"))
req, err := http.NewRequest("PATCH", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans/"+planID), buf)
if err != nil {
return err
}
req.SetBasicAuth(c.ClientID, c.Secret)
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
return c.SendWithAuth(req, nil)
}
// CreateBillingAgreement creates an agreement for specified plan
// Endpoint: POST /v1/payments/billing-agreements
func (c *Client) CreateBillingAgreement(a BillingAgreement) (*CreateAgreementResp, error) {
// PayPal needs only ID, so we will remove all fields except Plan ID
a.Plan = BillingPlan{
ID: a.Plan.ID,
}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements"), a)
response := &CreateAgreementResp{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// ExecuteApprovedAgreement - Use this call to execute (complete) a PayPal agreement that has been approved by the payer.
// Endpoint: POST /v1/payments/billing-agreements/token/agreement-execute
func (c *Client) ExecuteApprovedAgreement(token string) (*ExecuteAgreementResponse, error) {
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements/"+token+"/agreement-execute"), nil)
if err != nil {
return &ExecuteAgreementResponse{}, err
}
req.SetBasicAuth(c.ClientID, c.Secret)
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
e := ExecuteAgreementResponse{}
if err = c.SendWithAuth(req, &e); err != nil {
return &e, err
}
if e.ID == "" {
return &e, errors.New("Unable to execute agreement with token=" + token)
}
return &e, err
}

108
billing_test.go Normal file
View File

@ -0,0 +1,108 @@
package paypalsdk_test
import (
"fmt"
"time"
pp "github.com/logpacker/PayPal-Go-SDK"
)
func BillingExample() {
plan := pp.BillingPlan{
Name: "Plan with Regular and Trial Payment Definitions",
Description: "Plan with regular and trial payment definitions.",
Type: "fixed",
PaymentDefinitions: []pp.PaymentDefinition{
pp.PaymentDefinition{
Name: "Regular payment definition",
Type: "REGULAR",
Frequency: "MONTH",
FrequencyInterval: "2",
Amount: pp.AmountPayout{
Value: "100",
Currency: "USD",
},
Cycles: "12",
ChargeModels: []pp.ChargeModel{
pp.ChargeModel{
Type: "SHIPPING",
Amount: pp.AmountPayout{
Value: "10",
Currency: "USD",
},
},
pp.ChargeModel{
Type: "TAX",
Amount: pp.AmountPayout{
Value: "12",
Currency: "USD",
},
},
},
},
pp.PaymentDefinition{
Name: "Trial payment definition",
Type: "trial",
Frequency: "week",
FrequencyInterval: "5",
Amount: pp.AmountPayout{
Value: "9.19",
Currency: "USD",
},
Cycles: "2",
ChargeModels: []pp.ChargeModel{
pp.ChargeModel{
Type: "SHIPPING",
Amount: pp.AmountPayout{
Value: "1",
Currency: "USD",
},
},
pp.ChargeModel{
Type: "TAX",
Amount: pp.AmountPayout{
Value: "2",
Currency: "USD",
},
},
},
},
},
MerchantPreferences: &pp.MerchantPreferences{
SetupFee: &pp.AmountPayout{
Value: "1",
Currency: "USD",
},
ReturnURL: "http://www.paypal.com",
CancelURL: "http://www.paypal.com/cancel",
AutoBillAmount: "YES",
InitialFailAmountAction: "CONTINUE",
MaxFailAttempts: "0",
},
}
c, err := pp.NewClient("clientID", "secretID", pp.APIBaseSandBox)
if err != nil {
panic(err)
}
_, err = c.GetAccessToken()
if err != nil {
panic(err)
}
planResp, err := c.CreateBillingPlan(plan)
if err != nil {
panic(err)
}
err = c.ActivatePlan(planResp.ID)
fmt.Println(err)
agreement := pp.BillingAgreement{
Name: "Fast Speed Agreement",
Description: "Agreement for Fast Speed Plan",
StartDate: pp.JSONTime(time.Now().Add(time.Hour * 24)),
Plan: pp.BillingPlan{ID: planResp.ID},
Payer: pp.Payer{
PaymentMethod: "paypal",
},
}
resp, err := c.CreateBillingAgreement(agreement)
fmt.Println(err, resp)
}

113
client.go
View File

@ -1,19 +1,19 @@
package paypal
package paypalsdk
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"time"
)
// NewClient returns new Client struct
// APIBase is a base API URL, for testing you can use paypal.APIBaseSandBox
// APIBase is a base API URL, for testing you can use paypalsdk.APIBaseSandBox
func NewClient(clientID string, secret string, APIBase string) (*Client, error) {
if clientID == "" || secret == "" || APIBase == "" {
return nil, errors.New("ClientID, Secret and APIBase are required to create a Client")
@ -30,25 +30,25 @@ func NewClient(clientID string, secret string, APIBase string) (*Client, error)
// GetAccessToken returns struct of TokenResponse
// No need to call SetAccessToken to apply new access token for current Client
// Endpoint: POST /v1/oauth2/token
func (c *Client) GetAccessToken(ctx context.Context) (*TokenResponse, error) {
func (c *Client) GetAccessToken() (*TokenResponse, error) {
buf := bytes.NewBuffer([]byte("grant_type=client_credentials"))
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/oauth2/token"), buf)
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/oauth2/token"), buf)
if err != nil {
return &TokenResponse{}, err
}
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
response := &TokenResponse{}
err = c.SendWithBasicAuth(req, response)
t := TokenResponse{}
err = c.SendWithBasicAuth(req, &t)
// Set Token fur current Client
if response.Token != "" {
c.Token = response
c.tokenExpiresAt = time.Now().Add(time.Duration(response.ExpiresIn) * time.Second)
if t.Token != "" {
c.Token = &t
c.tokenExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second)
}
return response, err
return &t, err
}
// SetHTTPClient sets *http.Client to current client
@ -65,19 +65,13 @@ func (c *Client) SetAccessToken(token string) {
}
// SetLog will set/change the output destination.
// If log file is set paypal will log all requests and responses to this Writer
// If log file is set paypalsdk will log all requests and responses to this Writer
func (c *Client) SetLog(log io.Writer) {
c.Log = log
}
// SetReturnRepresentation enables verbose response
// Verbose response: https://developer.paypal.com/docs/api/orders/v2/#orders-authorize-header-parameters
func (c *Client) SetReturnRepresentation() {
c.returnRepresentation = true
}
// Send makes a request to the API, the response body will be
// unmarshalled into v, or if v is an io.Writer, the response will
// unmarshaled into v, or if v is an io.Writer, the response will
// be written to it without decoding
func (c *Client) Send(req *http.Request, v interface{}) error {
var (
@ -94,50 +88,33 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
if req.Header.Get("Content-type") == "" {
req.Header.Set("Content-type", "application/json")
}
if c.returnRepresentation {
req.Header.Set("Prefer", "return=representation")
}
if c.Log != nil {
if reqDump, err := httputil.DumpRequestOut(req, true); err == nil {
c.Log.Write([]byte(fmt.Sprintf("Request: %s\n", reqDump)))
}
}
resp, err = c.Client.Do(req)
c.log(req, resp)
if err != nil {
return err
}
if c.Log != nil {
if respDump, err := httputil.DumpResponse(resp, true); err == nil {
c.Log.Write([]byte(fmt.Sprintf("Response from %s: %s\n", req.URL, respDump)))
}
}
defer func(Body io.ReadCloser) error {
return Body.Close()
}(resp.Body)
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
errResp := &ErrorResponse{Response: resp}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err == nil && len(data) > 0 {
err := json.Unmarshal(data, errResp)
if err != nil {
return err
}
json.Unmarshal(data, errResp)
}
return errResp
}
if v == nil {
return nil
}
if w, ok := v.(io.Writer); ok {
_, err := io.Copy(w, resp.Body)
return err
io.Copy(w, resp.Body)
return nil
}
return json.NewDecoder(resp.Body).Decode(v)
@ -148,25 +125,17 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
// making the main request
// client.Token will be updated when changed
func (c *Client) SendWithAuth(req *http.Request, v interface{}) error {
// c.Lock()
c.mu.Lock()
// Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)`
// to happen outside of the locked section.
if c.Token == nil || (!c.tokenExpiresAt.IsZero() && time.Until(c.tokenExpiresAt) < RequestNewTokenBeforeExpiresIn) {
// c.Token will be updated in GetAccessToken call
if _, err := c.GetAccessToken(req.Context()); err != nil {
// c.Unlock()
c.mu.Unlock()
return err
if c.Token != nil {
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
// c.Token will be updated in GetAccessToken call
if _, err := c.GetAccessToken(); err != nil {
return err
}
}
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
}
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
// Unlock the client mutex before sending the request, this allows multiple requests
// to be in progress at the same time.
// c.Unlock()
c.mu.Unlock()
return c.Send(req, v)
}
@ -179,14 +148,34 @@ func (c *Client) SendWithBasicAuth(req *http.Request, v interface{}) error {
// NewRequest constructs a request
// Convert payload to a JSON
func (c *Client) NewRequest(ctx context.Context, method, url string, payload interface{}) (*http.Request, error) {
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.NewRequestWithContext(ctx, method, url, buf)
return http.NewRequest(method, url, buf)
}
// log will dump request and response to the log file
func (c *Client) log(r *http.Request, resp *http.Response) {
if c.Log != nil {
var (
reqDump string
respDump []byte
)
if r != nil {
reqDump = fmt.Sprintf("%s %s. Data: %s", r.Method, r.URL.String(), r.Form.Encode())
}
if resp != nil {
respDump, _ = httputil.DumpResponse(resp, true)
}
c.Log.Write([]byte(fmt.Sprintf("Request: %s\nResponse: %s\n", reqDump, string(respDump))))
}
}

View File

@ -1,94 +0,0 @@
package paypal
import (
"context"
"fmt"
"math/rand"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const alphabet = "abcedfghijklmnopqrstuvwxyz"
var testClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
var testSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
func RandomString(n int) string {
var sb strings.Builder
k := len(alphabet)
for i := 0; i < n; i++ {
c := alphabet[rand.Intn(k)]
sb.WriteByte(c)
}
return sb.String()
}
func createRandomProduct(t *testing.T) Product {
//create a product
productData := Product{
Name: RandomString(10),
Description: RandomString(100),
Category: ProductCategorySoftware,
Type: ProductTypeService,
ImageUrl: "https://example.com/image.png",
HomeUrl: "https://example.com",
}
return productData
}
// this is a simple copy of the SendWithAuth method, used to
// test the Lock and Unlock methods of the private mutex field
// of Client structure.
func (c *Client) sendWithAuth(req *http.Request, v interface{}) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.Token == nil || (!c.tokenExpiresAt.IsZero() && time.Until(c.tokenExpiresAt) < RequestNewTokenBeforeExpiresIn) {
if _, err := c.GetAccessToken(req.Context()); err != nil {
return err
}
}
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
return c.Send(req, v)
}
// this method is used to invoke the sendWithAuth method, which will then check
// operationally the privated mutex field of Client structure.
func (c *Client) createProduct(ctx context.Context, product Product) (*CreateProductResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/catalogs/products"), product)
response := &CreateProductResponse{}
if err != nil {
return response, err
}
err = c.sendWithAuth(req, response)
return response, err
}
func TestClientMutex(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
// Operational testing of the private mutex field
n_iter := 2
errs := make(chan error)
for i := 0; i < n_iter; i++ {
go func() {
_, err := c.createProduct(context.Background(), createRandomProduct(t))
errs <- err
}()
}
for i := 0; i < n_iter; i++ {
err := <-errs
assert.Equal(t, nil, err)
}
}

118
const.go
View File

@ -1,118 +0,0 @@
package paypal
type SubscriptionPlanStatus string
const (
SubscriptionPlanStatusCreated SubscriptionPlanStatus = "CREATED"
SubscriptionPlanStatusInactive SubscriptionPlanStatus = "INACTIVE"
SubscriptionPlanStatusActive SubscriptionPlanStatus = "ACTIVE"
)
type BillingPlanStatus string
const (
BillingPlanStatusActive BillingPlanStatus = "ACTIVE"
)
type IntervalUnit string
const (
IntervalUnitDay IntervalUnit = "DAY"
IntervalUnitWeek IntervalUnit = "WEEK"
IntervalUnitMonth IntervalUnit = "MONTH"
IntervalUnitYear IntervalUnit = "YEAR"
)
type TenureType string
const (
TenureTypeRegular TenureType = "REGULAR"
TenureTypeTrial TenureType = "TRIAL"
)
type SetupFeeFailureAction string
const (
SetupFeeFailureActionContinue SetupFeeFailureAction = "CONTINUE"
SetupFeeFailureActionCancel SetupFeeFailureAction = "CANCEL"
)
type ShippingPreference string
const (
ShippingPreferenceGetFromFile ShippingPreference = "GET_FROM_FILE"
ShippingPreferenceNoShipping ShippingPreference = "NO_SHIPPING"
ShippingPreferenceSetProvidedAddress ShippingPreference = "SET_PROVIDED_ADDRESS"
)
type UserAction string
const (
UserActionContinue UserAction = "CONTINUE"
UserActionPayNow UserAction = "PAY_NOW"
UserActionSubscribeNow UserAction = "SUBSCRIBE_NOW"
)
type SubscriptionStatus string
const (
SubscriptionStatusApprovalPending SubscriptionStatus = "APPROVAL_PENDING"
SubscriptionStatusApproved SubscriptionStatus = "APPROVED"
SubscriptionStatusActive SubscriptionStatus = "ACTIVE"
SubscriptionStatusSuspended SubscriptionStatus = "SUSPENDED"
SubscriptionStatusCancelled SubscriptionStatus = "CANCELLED"
SubscriptionStatusExpired SubscriptionStatus = "EXPIRED"
)
//Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-transaction
type SubscriptionTransactionStatus string
const (
SubscriptionCaptureStatusCompleted SubscriptionTransactionStatus = "COMPLETED"
SubscriptionCaptureStatusDeclined SubscriptionTransactionStatus = "DECLINED"
SubscriptionCaptureStatusPartiallyRefunded SubscriptionTransactionStatus = "PARTIALLY_REFUNDED"
SubscriptionCaptureStatusPending SubscriptionTransactionStatus = "PENDING"
SubscriptionCaptureStatusRefunded SubscriptionTransactionStatus = "REFUNDED"
)
type CaptureType string
const (
CaptureTypeOutstandingBalance CaptureType = "OUTSTANDING_BALANCE"
)
type ProductType string
type ProductCategory string //Doc: https://developer.paypal.com/docs/api/catalog-products/v1/#definition-product_category
const (
ProductTypePhysical ProductType = "PHYSICAL"
ProductTypeDigital ProductType = "DIGITAL"
ProductTypeService ProductType = "SERVICE"
ProductCategorySoftware ProductCategory = "SOFTWARE"
ProductCategorySoftwareComputerAndDataProcessingServices ProductCategory = "COMPUTER_AND_DATA_PROCESSING_SERVICES"
ProductCategorySoftwareDigitalGames ProductCategory = "DIGITAL_GAMES"
ProductCategorySoftwareGameSoftware ProductCategory = "GAME_SOFTWARE"
ProductCategorySoftwareGames ProductCategory = "GAMES"
ProductCategorySoftwareGeneral ProductCategory = "GENERAL"
ProductCategorySoftwareGraphicAndCommercialDesign ProductCategory = "GRAPHIC_AND_COMMERCIAL_DESIGN"
ProductCategorySoftwareOemSoftware ProductCategory = "OEM_SOFTWARE"
ProductCategorySoftwareOnlineGaming ProductCategory = "ONLINE_GAMING"
ProductCategorySoftwareOnlineGamingCurrency ProductCategory = "ONLINE_GAMING_CURRENCY"
ProductCategorySoftwareOnlineServices ProductCategory = "ONLINE_SERVICES"
ProductCategorySoftwareOther ProductCategory = "OTHER"
ProductCategorySoftwareServices ProductCategory = "SERVICES"
)
type PayeePreferred string // Doc: https://developer.paypal.com/api/orders/v2/#definition-payment_method
const (
PayeePreferredUnrestricted PayeePreferred = "UNRESTRICTED"
PayeePreferredImmediatePaymentRequired PayeePreferred = "IMMEDIATE_PAYMENT_REQUIRED"
)
type StandardEntryClassCode string // Doc: https://developer.paypal.com/api/orders/v2/#definition-payment_method
const (
StandardEntryClassCodeTel StandardEntryClassCode="TEL"
StandardEntryClassCodeWeb StandardEntryClassCode="WEB"
StandardEntryClassCodeCcd StandardEntryClassCode="CCD"
StandardEntryClassCodePpd StandardEntryClassCode="PPD"
)

10
doc.go
View File

@ -1,10 +1,10 @@
/*
Package paypal provides a wrapper to PayPal API (https://developer.paypal.com/webapps/developer/docs/api/).
The first thing you do is to create a Client (you can select API base URL using paypal contants).
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox)
Package paypalsdk provides a wrapper to PayPal API (https://developer.paypal.com/webapps/developer/docs/api/).
The first thing you do is to create a Client (you can select API base URL using paypalsdk contants).
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
Then you can get an access token from PayPal:
accessToken, err := c.GetAccessToken()
After you have an access token you can call built-in functions to get data from PayPal.
paypal will assign all responses to go structures.
paypalsdk will assign all responses to go structures.
*/
package paypal
package paypalsdk

51
example_test.go Normal file
View File

@ -0,0 +1,51 @@
package paypalsdk_test
import paypalsdk "github.com/logpacker/PayPal-Go-SDK"
func Example() {
// Initialize client
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
if err != nil {
panic(err)
}
// Retrieve access token
_, err = c.GetAccessToken()
if err != nil {
panic(err)
}
// Create credit card payment
p := paypalsdk.Payment{
Intent: "sale",
Payer: &paypalsdk.Payer{
PaymentMethod: "credit_card",
FundingInstruments: []paypalsdk.FundingInstrument{{
CreditCard: &paypalsdk.CreditCard{
Number: "4111111111111111",
Type: "visa",
ExpireMonth: "11",
ExpireYear: "2020",
CVV2: "777",
FirstName: "John",
LastName: "Doe",
},
}},
},
Transactions: []paypalsdk.Transaction{{
Amount: &paypalsdk.Amount{
Currency: "USD",
Total: "7.00",
},
Description: "My Payment",
}},
RedirectURLs: &paypalsdk.RedirectURLs{
ReturnURL: "http://...",
CancelURL: "http://...",
},
}
_, err = c.CreatePayment(p)
if err != nil {
panic(err)
}
}

View File

@ -1,61 +0,0 @@
package paypal
import (
"fmt"
"time"
)
const format = "2006-01-02T15:04:05Z"
// Filter type
type Filter struct {
fields []fmt.Stringer
}
func (s *Filter) String() string {
var filter string
for i, f := range s.fields {
if i == 0 {
filter = "?" + f.String()
} else {
filter = filter + "&" + f.String()
}
}
return filter
}
// TextField type
type TextField struct {
name string
Is string
}
func (d TextField) String() string {
return fmt.Sprintf("%s=%s", d.name, d.Is)
}
// TimeField type
type TimeField struct {
name string
Is time.Time
}
// String .
func (d TimeField) String() string {
return fmt.Sprintf("%s=%s", d.name, d.Is.UTC().Format(format))
}
// AddTextField .
func (s *Filter) AddTextField(field string) *TextField {
f := &TextField{name: field}
s.fields = append(s.fields, f)
return f
}
// AddTimeField .
func (s *Filter) AddTimeField(field string) *TimeField {
f := &TimeField{name: field}
s.fields = append(s.fields, f)
return f
}

View File

@ -1,44 +0,0 @@
package paypal
import (
"testing"
"time"
)
func TestFilter_AddTextField(t *testing.T) {
filter := &Filter{}
filter.AddTextField("sort_by").Is = "create_time"
filter.AddTextField("count").Is = "30"
filter.AddTextField("sort_order").Is = "desc"
expected := "?sort_by=create_time&count=30&sort_order=desc"
if filter.String() != expected {
t.Errorf("filter string was %s, wanted %s", filter.String(), expected)
}
}
func TestFilter_AddTimeField(t *testing.T) {
filter := &Filter{}
startTime := time.Time{}
endTime := startTime.Add(time.Hour * 24 * 30)
filter.AddTimeField("start_time").Is = startTime
filter.AddTimeField("stop_time").Is = endTime
expected := "?start_time=0001-01-01T00:00:00Z&stop_time=0001-01-31T00:00:00Z"
if filter.String() != expected {
t.Errorf("filter string was %s, wanted %s", filter.String(), expected)
}
}
func TestFilter_AddMixedFields(t *testing.T) {
filter := &Filter{}
startTime := time.Time{}
endTime := startTime.Add(time.Hour * 24 * 30)
filter.AddTimeField("stop_time").Is = endTime
filter.AddTextField("count").Is = "30"
expected := "?stop_time=0001-01-31T00:00:00Z&count=30"
if filter.String() != expected {
t.Errorf("filter string was %s, wanted %s", filter.String(), expected)
}
}

11
go.mod
View File

@ -1,11 +0,0 @@
module euphoria-laxis.fr/go-packages/paypale/v4
go 1.23
require github.com/stretchr/testify v1.9.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
go.sum
View File

@ -1,10 +0,0 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,24 +1,72 @@
package paypal
package paypalsdk
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// GrantNewAccessTokenFromAuthCode - Use this call to grant a new access token, using the previously obtained authorization code.
// Endpoint: POST /v1/identity/openidconnect/tokenservice
func (c *Client) GrantNewAccessTokenFromAuthCode(code string, redirectURI string) (*TokenResponse, error) {
token := &TokenResponse{}
q := url.Values{}
q.Set("grant_type", "authorization_code")
q.Set("code", code)
q.Set("redirect_uri", redirectURI)
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/identity/openidconnect/tokenservice"), strings.NewReader(q.Encode()))
if err != nil {
return token, err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if err = c.SendWithBasicAuth(req, token); err != nil {
return token, err
}
return token, nil
}
// GrantNewAccessTokenFromRefreshToken - Use this call to grant a new access token, using a refresh token.
// Endpoint: POST /v1/identity/openidconnect/tokenservice
func (c *Client) GrantNewAccessTokenFromRefreshToken(refreshToken string) (*TokenResponse, error) {
type request struct {
GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"`
}
token := &TokenResponse{}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/identity/openidconnect/tokenservice"), request{GrantType: "refresh_token", RefreshToken: refreshToken})
if err != nil {
return token, err
}
if err = c.SendWithAuth(req, token); err != nil {
return token, err
}
return token, nil
}
// GetUserInfo - Use this call to retrieve user profile attributes.
// Endpoint: GET /v1/identity/openidconnect/userinfo/?schema=<Schema>
func (c *Client) GetUserInfo(ctx context.Context, schema string) (*UserInfo, error) {
u := &UserInfo{}
// Pass the schema that is used to return as per openidconnect protocol. The only supported schema value is openid.
func (c *Client) GetUserInfo(schema string) (*UserInfo, error) {
u := UserInfo{}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/identity/openidconnect/userinfo/?schema=", schema), nil)
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/identity/openidconnect/userinfo/?schema=", schema), nil)
if err != nil {
return u, err
return &u, err
}
if err = c.SendWithAuth(req, u); err != nil {
return u, err
if err = c.SendWithAuth(req, &u); err != nil {
return &u, err
}
return u, nil
return &u, nil
}

437
integration_test.go Normal file
View File

@ -0,0 +1,437 @@
// +build integration
package paypalsdk
import (
"testing"
)
// All test values are defined here
var testClientID = "AZgwu4yt5Ba0gyTu1dGBH3txHCJbMuFNvrmQxBaQbfDncDiCs6W_rwJD8Ir-0pZrN-_eq7n9zVd8Y-5f"
var testSecret = "EBzA1wRl5t73OMugOieDj_tI3vihfJmGl47ukQT-cpctooIzDu0K7IPESNC0cKodlLSOXzwI8qXSM0rd"
var testAuthID = "2DC87612EK520411B"
var testOrderID = "O-0PW72302W3743444R"
var testSaleID = "4CF18861HF410323U"
var testPaymentID = "PAY-5YK922393D847794YKER7MUI"
var testPayerID = "CR87QHB7JTRSC"
var testUserID = "https://www.paypal.com/webapps/auth/identity/user/WEssgRpQij92sE99_F9MImvQ8FPYgUEjrvCja2qH2H8"
var testCardID = "CARD-54E6956910402550WKGRL6EA"
func TestGetAccessToken(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
token, err := c.GetAccessToken()
if err != nil {
t.Errorf("Not expected error for GetAccessToken(), got %s", err.Error())
}
if token.Token == "" {
t.Errorf("Expected non-empty token for GetAccessToken()")
}
}
func TestGetAuthorization(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.GetAuthorization(testAuthID)
if err == nil {
t.Errorf("GetAuthorization expects error")
}
}
func TestCaptureAuthorization(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.CaptureAuthorization(testAuthID, &Amount{Total: "200", Currency: "USD"}, true)
if err == nil {
t.Errorf("Auth is expired, 400 error must be returned")
}
}
func TestVoidAuthorization(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.VoidAuthorization(testAuthID)
if err == nil {
t.Errorf("Auth is expired, 400 error must be returned")
}
}
func TestReauthorizeAuthorization(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.ReauthorizeAuthorization(testAuthID, &Amount{Total: "200", Currency: "USD"})
if err == nil {
t.Errorf("Reauthorization not allowed for this product, 500 error must be returned")
}
}
func TestGrantNewAccessTokenFromAuthCode(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
_, err := c.GrantNewAccessTokenFromAuthCode("123", "http://example.com/myapp/return.php")
if err == nil {
t.Errorf("GrantNewAccessTokenFromAuthCode must return error for invalid code")
}
}
func TestGrantNewAccessTokenFromRefreshToken(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
_, err := c.GrantNewAccessTokenFromRefreshToken("123")
if err == nil {
t.Errorf("GrantNewAccessTokenFromRefreshToken must return error for invalid refresh token")
}
}
func TestGetUserInfo(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
u, err := c.GetUserInfo("openid")
if u.ID != testUserID || err != nil {
t.Errorf("GetUserInfo must return valid test ID=%s, error: %v", testUserID, err)
}
}
func TestGetOrder(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.GetOrder(testOrderID)
if err == nil {
t.Errorf("GetOrder expects error")
}
}
func TestAuthorizeOrder(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.AuthorizeOrder(testOrderID, &Amount{Total: "7.00", Currency: "USD"})
if err == nil {
t.Errorf("Order is expired, 400 error must be returned")
}
}
func TestCaptureOrder(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.CaptureOrder(testOrderID, &Amount{Total: "100", Currency: "USD"}, true, nil)
if err == nil {
t.Errorf("Order is expired, 400 error must be returned")
}
}
func TestVoidOrder(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.VoidOrder(testOrderID)
if err == nil {
t.Errorf("Order is expired, 400 error must be returned")
}
}
func TestCreateDirectPaypalPayment(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
amount := Amount{
Total: "15.11",
Currency: "USD",
}
p, err := c.CreateDirectPaypalPayment(amount, "http://example.com", "http://example.com", "test payment")
if err != nil || p.ID == "" {
t.Errorf("Test paypal payment is not created, err: %v", err)
}
}
func TestCreatePayment(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
p := Payment{
Intent: "sale",
Payer: &Payer{
PaymentMethod: "credit_card",
FundingInstruments: []FundingInstrument{{
CreditCard: &CreditCard{
Number: "4111111111111111",
Type: "visa",
ExpireMonth: "11",
ExpireYear: "2020",
CVV2: "777",
FirstName: "John",
LastName: "Doe",
},
}},
},
Transactions: []Transaction{{
Amount: &Amount{
Currency: "USD",
Total: "10.00", // total cost including shipping
Details: Details{
Shipping: "3.00", // total shipping cost
Subtotal: "7.00", // total cost without shipping
},
},
Description: "My Payment",
ItemList: &ItemList{
Items: []Item{
Item{
Quantity: "2",
Price: "3.50",
Currency: "USD",
Name: "Product 1",
},
},
},
}},
RedirectURLs: &RedirectURLs{
ReturnURL: "http://..",
CancelURL: "http://..",
},
}
_, err := c.CreatePayment(p)
if err == nil {
t.Errorf("Expected error")
}
}
func TestGetPayment(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.GetPayment(testPaymentID)
if err == nil {
t.Errorf("404 for this payment ID")
}
}
func TestGetPayments(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.GetPayments()
if err != nil {
t.Errorf("Nil error expected, got: %s", err.Error())
}
}
func TestPatchPayment(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
p := Payment{
Intent: "sale",
Payer: &Payer{
PaymentMethod: "paypal",
},
Transactions: []Transaction{{
Amount: &Amount{
Currency: "USD",
Total: "10.00", // total cost including shipping
Details: Details{
Shipping: "3.00", // total shipping cost
Subtotal: "7.00", // total cost without shipping
},
},
Description: "My Payment",
ItemList: &ItemList{
Items: []Item{
Item{
Quantity: "2",
Price: "3.50",
Currency: "USD",
Name: "Product 1",
},
},
},
Custom: "First value",
}},
RedirectURLs: &RedirectURLs{
ReturnURL: "http://..",
CancelURL: "http://..",
},
}
NewPayment, err := c.CreatePayment(p)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
PaymentID := NewPayment.ID
pp := []PaymentPatch{
{
Operation: "replace",
Path: "/transactions/0/custom",
Value: "Replaced Value",
},
}
RevisedPayment, errpp := c.PatchPayment(PaymentID, pp)
if errpp != nil {
t.Errorf("Unexpected error when patching %v", errpp)
}
if RevisedPayment.Transactions != nil &&
len(RevisedPayment.Transactions) > 0 &&
RevisedPayment.Transactions[0].Custom != "" {
if RevisedPayment.Transactions[0].Custom != "Replaced Value" {
t.Errorf("Patched payment value failed to be patched %v", RevisedPayment.Transactions[0].Custom)
}
}
}
func TestExecuteApprovedPayment(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.ExecuteApprovedPayment(testPaymentID, testPayerID)
if err == nil {
t.Errorf("404 for this payment ID")
}
}
func TestCreateSinglePayout(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
payout := Payout{
SenderBatchHeader: &SenderBatchHeader{
EmailSubject: "Subject will be displayed on PayPal",
},
Items: []PayoutItem{
{
RecipientType: "EMAIL",
Receiver: "single-email-payout@mail.com",
Amount: &AmountPayout{
Value: "15.11",
Currency: "USD",
},
Note: "Optional note",
SenderItemID: "Optional Item ID",
},
},
}
_, err := c.CreateSinglePayout(payout)
if err != nil {
t.Errorf("Test single payout is not created, error: %v", err)
}
}
func TestGetSale(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.GetSale(testSaleID)
if err == nil {
t.Errorf("404 must be returned for ID=%s", testSaleID)
}
}
func TestRefundSale(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.RefundSale(testSaleID, nil)
if err == nil {
t.Errorf("404 must be returned for ID=%s", testSaleID)
}
_, err = c.RefundSale(testSaleID, &Amount{Total: "7.00", Currency: "USD"})
if err == nil {
t.Errorf("404 must be returned for ID=%s", testSaleID)
}
}
func TestGetRefund(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.GetRefund("1")
if err == nil {
t.Errorf("404 must be returned for ID=%s", testSaleID)
}
}
func TestStoreCreditCard(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
r1, e1 := c.StoreCreditCard(CreditCard{})
if e1 == nil || r1 != nil {
t.Errorf("Error is expected for invalid CC")
}
r2, e2 := c.StoreCreditCard(CreditCard{
Number: "4417119669820331",
Type: "visa",
ExpireMonth: "11",
ExpireYear: "2020",
CVV2: "874",
FirstName: "Foo",
LastName: "Bar",
})
if e2 != nil || r2 == nil {
t.Errorf("200 code expected for valid CC card. Error: %v", e2)
}
}
func TestDeleteCreditCard(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
e1 := c.DeleteCreditCard("")
if e1 == nil {
t.Errorf("Error is expected for invalid CC ID")
}
}
func TestGetCreditCard(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
r1, e1 := c.GetCreditCard("BBGGG")
if e1 == nil || r1 != nil {
t.Errorf("Error is expected for invalid CC, got CC %v", r1)
}
}
func TestGetCreditCards(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
r1, e1 := c.GetCreditCards(nil)
if e1 != nil || r1 == nil {
t.Errorf("200 code expected. Error: %v", e1)
}
r2, e2 := c.GetCreditCards(&CreditCardsFilter{
Page: 2,
PageSize: 7,
})
if e2 != nil || r2 == nil {
t.Errorf("200 code expected. Error: %v", e2)
}
}
func TestPatchCreditCard(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
r1, e1 := c.PatchCreditCard(testCardID, nil)
if e1 == nil || r1 != nil {
t.Errorf("Error is expected for empty update info")
}
}

View File

@ -1,38 +0,0 @@
package paypal
import (
"context"
"fmt"
)
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
// Endpoint: POST /v2/invoicing/generate-next-invoice-number
func (c *Client) GenerateInvoiceNumber(ctx context.Context) (*InvoiceNumber, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/invoicing/generate-next-invoice-number"), nil)
nextInvoiceNumber := &InvoiceNumber{}
if err != nil {
return nextInvoiceNumber, err
}
if err = c.SendWithAuth(req, nextInvoiceNumber); err != nil {
return nextInvoiceNumber, err
}
return nextInvoiceNumber, nil
}
// GetInvoiceDetails: show invoice details for a particular invoice by ID.
// Endpoint: GET /v2/invoicing/invoices/{invoice_id}
func (c *Client) GetInvoiceDetails(ctx context.Context, invoiceID string) (*Invoice, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/invoicing/invoices/", invoiceID), nil)
invoice := &Invoice{}
if err != nil {
return invoice, err
}
if err = c.SendWithAuth(req, invoice); err != nil {
return invoice, err
}
return invoice, nil
}

View File

@ -1,406 +0,0 @@
package paypal_test
import (
"context"
"encoding/json"
"testing"
"euphoria-laxis.fr/go-packages/paypale/v4"
"github.com/stretchr/testify/assert"
)
// All test values are defined here
var devTestClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
var devTestSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
var devAPIBaseSandBox = "https://api.sandbox.paypal.com"
func TestGenerateInvoiceNumber(t *testing.T) {
ctx := context.Background()
c, _ := paypal.NewClient(devTestClientID, devTestSecret, devAPIBaseSandBox)
_, err := c.GetAccessToken(ctx)
assert.Equal(t, nil, err)
_, err = c.GenerateInvoiceNumber(ctx)
assert.Equal(t, nil, err)
}
func assertTwoInvoices(t *testing.T, invoice paypal.Invoice, testInvoice paypal.Invoice) {
// additional_recipients
assert.Equal(t, len(invoice.AdditionalRecipients), len(testInvoice.AdditionalRecipients))
// additional_recipients --> email_address !! EQUALITY OF SPLICE OF STRUCT REMAINING !!
// amount
assert.Equal(t, invoice.AmountSummary.Currency, testInvoice.AmountSummary.Currency)
assert.Equal(t, invoice.AmountSummary.Value, testInvoice.AmountSummary.Value)
// amount-->breakdown-->custom-->amount
assert.Equal(t, invoice.AmountSummary.Breakdown.Custom.Amount.Currency, testInvoice.AmountSummary.Breakdown.Custom.Amount.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.Custom.Amount.Value, testInvoice.AmountSummary.Breakdown.Custom.Amount.Value)
// amount-->breakdown-->custom-->label
assert.Equal(t, invoice.AmountSummary.Breakdown.Custom.Label, testInvoice.AmountSummary.Breakdown.Custom.Label)
// amount-->breakdown-->discount-->amount
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Currency, testInvoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Value, testInvoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Value)
// amount-->breakdown-->discount-->percent
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.Percent, testInvoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.Percent)
// amount-->breakdown-->discount-->item_discount
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.ItemDiscount.Currency, testInvoice.AmountSummary.Breakdown.Discount.ItemDiscount.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.ItemDiscount.Value, testInvoice.AmountSummary.Breakdown.Discount.ItemDiscount.Value)
// amount-->breakdown-->item_total
assert.Equal(t, invoice.AmountSummary.Breakdown.ItemTotal.Currency, testInvoice.AmountSummary.Breakdown.ItemTotal.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.ItemTotal.Value, testInvoice.AmountSummary.Breakdown.ItemTotal.Value)
// amount-->breakdown-->shipping-->amount
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Amount.Currency, testInvoice.AmountSummary.Breakdown.Shipping.Amount.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Amount.Value, testInvoice.AmountSummary.Breakdown.Shipping.Amount.Value)
// amount-->breakdown-->shipping-->tax
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Currency, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Value, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Value)
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.ID, testInvoice.AmountSummary.Breakdown.Shipping.Tax.ID)
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Name, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Name)
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Percent, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Percent)
// amount-->breakdown-->tax_total
assert.Equal(t, invoice.AmountSummary.Breakdown.TaxTotal.Currency, testInvoice.AmountSummary.Breakdown.TaxTotal.Currency)
assert.Equal(t, invoice.AmountSummary.Breakdown.TaxTotal.Value, testInvoice.AmountSummary.Breakdown.TaxTotal.Value)
// configuration
assert.Equal(t, invoice.Configuration.AllowTip, testInvoice.Configuration.AllowTip)
assert.Equal(t, invoice.Configuration.TaxCalculatedAfterDiscount, testInvoice.Configuration.TaxCalculatedAfterDiscount)
assert.Equal(t, invoice.Configuration.TaxInclusive, testInvoice.Configuration.TaxInclusive)
assert.Equal(t, invoice.Configuration.TemplateId, testInvoice.Configuration.TemplateId)
// configuration --> partial_payment
assert.Equal(t, invoice.Configuration.PartialPayment.AllowPartialPayment, testInvoice.Configuration.PartialPayment.AllowPartialPayment)
assert.Equal(t, invoice.Configuration.PartialPayment.MinimumAmountDue.Currency, testInvoice.Configuration.PartialPayment.MinimumAmountDue.Currency)
assert.Equal(t, invoice.Configuration.PartialPayment.MinimumAmountDue.Value, testInvoice.Configuration.PartialPayment.MinimumAmountDue.Value)
// detail
assert.Equal(t, invoice.Detail.CurrencyCode, testInvoice.Detail.CurrencyCode)
assert.Equal(t, invoice.Detail.InvoiceDate, testInvoice.Detail.InvoiceDate)
assert.Equal(t, invoice.Detail.InvoiceNumber, testInvoice.Detail.InvoiceNumber)
assert.Equal(t, invoice.Detail.Memo, testInvoice.Detail.Memo)
assert.Equal(t, invoice.Detail.Note, testInvoice.Detail.Note)
assert.Equal(t, invoice.Detail.Reference, testInvoice.Detail.Reference)
assert.Equal(t, invoice.Detail.TermsAndConditions, testInvoice.Detail.TermsAndConditions)
// detail --> attachments !! EQUALITY OF SPLICE OF STRUCT REMAINING !!
assert.Equal(t, len(invoice.Detail.Attachments), len(testInvoice.Detail.Attachments))
// detail --> metadata
assert.Equal(t, invoice.Detail.Metadata.CancelTime, testInvoice.Detail.Metadata.CancelTime)
assert.Equal(t, invoice.Detail.Metadata.CancellledTimeBy, testInvoice.Detail.Metadata.CancellledTimeBy)
assert.Equal(t, invoice.Detail.Metadata.CreateTime, testInvoice.Detail.Metadata.CreateTime)
assert.Equal(t, invoice.Detail.Metadata.CreatedBy, testInvoice.Detail.Metadata.CreatedBy)
assert.Equal(t, invoice.Detail.Metadata.CreatedByFlow, testInvoice.Detail.Metadata.CreatedByFlow)
assert.Equal(t, invoice.Detail.Metadata.FirstSentTime, testInvoice.Detail.Metadata.FirstSentTime)
assert.Equal(t, invoice.Detail.Metadata.InvoicerViewUrl, testInvoice.Detail.Metadata.InvoicerViewUrl)
assert.Equal(t, invoice.Detail.Metadata.LastSentBy, testInvoice.Detail.Metadata.LastSentBy)
assert.Equal(t, invoice.Detail.Metadata.LastSentTime, testInvoice.Detail.Metadata.LastSentTime)
assert.Equal(t, invoice.Detail.Metadata.LastUpdateTime, testInvoice.Detail.Metadata.LastUpdateTime)
assert.Equal(t, invoice.Detail.Metadata.LastUpdatedBy, testInvoice.Detail.Metadata.LastUpdatedBy)
assert.Equal(t, invoice.Detail.Metadata.RecipientViewUrl, testInvoice.Detail.Metadata.RecipientViewUrl)
// detail --> payment_term
assert.Equal(t, invoice.Detail.PaymentTerm.DueDate, testInvoice.Detail.PaymentTerm.DueDate)
assert.Equal(t, invoice.Detail.PaymentTerm.TermType, testInvoice.Detail.PaymentTerm.TermType)
// due_amount
assert.Equal(t, invoice.DueAmount.Currency, testInvoice.DueAmount.Currency)
assert.Equal(t, invoice.DueAmount.Value, testInvoice.DueAmount.Value)
// gratuity
assert.Equal(t, invoice.Gratuity.Currency, testInvoice.Gratuity.Currency)
assert.Equal(t, invoice.Gratuity.Value, testInvoice.Gratuity.Value)
// id
assert.Equal(t, invoice.ID, testInvoice.ID)
// invoicer
assert.Equal(t, invoice.Invoicer.AdditionalNotes, testInvoice.Invoicer.AdditionalNotes)
assert.Equal(t, invoice.Invoicer.EmailAddress, testInvoice.Invoicer.EmailAddress)
assert.Equal(t, invoice.Invoicer.LogoUrl, testInvoice.Invoicer.LogoUrl)
assert.Equal(t, invoice.Invoicer.TaxId, testInvoice.Invoicer.TaxId)
assert.Equal(t, invoice.Invoicer.Website, testInvoice.Invoicer.Website)
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
// invoicer --> phones
assert.Equal(t, len(invoice.Invoicer.Phones), len(testInvoice.Invoicer.Phones))
// items
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
assert.Equal(t, len(invoice.Items), len(testInvoice.Items))
// links
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
assert.Equal(t, len(invoice.Links), len(testInvoice.Links))
// parent_id
assert.Equal(t, invoice.ParentID, testInvoice.ParentID)
// payments
assert.Equal(t, invoice.Payments.PaidAmount.Currency, testInvoice.Payments.PaidAmount.Currency)
assert.Equal(t, invoice.Payments.PaidAmount.Value, testInvoice.Payments.PaidAmount.Value)
// payments --> transactions
assert.Equal(t, len(invoice.Payments.Transactions), len(testInvoice.Payments.Transactions))
// primary_recipients
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
assert.Equal(t, len(invoice.PrimaryRecipients), len(testInvoice.PrimaryRecipients))
// refunds
assert.Equal(t, invoice.Refunds.RefundAmount.Currency, testInvoice.Refunds.RefundAmount.Currency)
assert.Equal(t, invoice.Refunds.RefundAmount.Value, testInvoice.Refunds.RefundAmount.Value)
assert.Equal(t, len(invoice.Refunds.RefundDetails), len(testInvoice.Refunds.RefundDetails))
// status
assert.Equal(t, invoice.Status, testInvoice.Status)
}
func TestGetInvoice(t *testing.T) {
testInvoiceJSONData := []byte(`
{
"amount": {
"breakdown": {
"custom": {
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"label": "Packing Charges"
},
"discount": {
"invoice_discount": {
"amount": {
"currency_code": "USD",
"value": "-2.63"
},
"percent": "5"
},
"item_discount": {
"currency_code": "USD",
"value": "-7.50"
}
},
"item_total": {
"currency_code": "USD",
"value": "60.00"
},
"shipping": {
"amount": {
"currency_code": "USD",
"value": "10.00"
},
"tax": {
"amount": {
"currency_code": "USD",
"value": "0.73"
},
"id": "TAX-5XV24702TP4910056",
"name": "Sales Tax",
"percent": "7.25"
}
},
"tax_total": {
"currency_code": "USD",
"value": "4.34"
}
},
"currency_code": "USD",
"value": "74.21"
},
"configuration": {
"allow_tip": true,
"partial_payment": {
"allow_partial_payment": true,
"minimum_amount_due": {
"currency_code": "USD",
"value": "20.00"
}
},
"tax_calculated_after_discount": true,
"tax_inclusive": false,
"template_id": "TEMP-4NW98229SC0703920"
},
"detail": {
"additional_data": "2-4",
"archived": false,
"category_code": "SHIPPABLE",
"currency_code": "USD",
"group_draft": false,
"invoice_date": "2018-11-12",
"invoice_number": "0001",
"memo": "This is a long contract",
"metadata": {
"caller_type": "API_V2_INVOICE",
"create_time": "2022-10-25T16:54:50Z",
"created_by_flow": "REGULAR_SINGLE",
"invoicer_view_url": "https://www.sandbox.paypal.com/invoice/details/INV2-XFXV-YW42-ZANU-4F33",
"last_update_time": "2022-10-25T16:54:50Z",
"recipient_view_url": "https://www.sandbox.paypal.com/invoice/p/#XFXVYW42ZANU4F33"
},
"note": "Thank you for your business.",
"payment_term": {
"due_date": "2018-11-22",
"term_type": "NET_10"
},
"reference": "deal-ref",
"viewed_by_recipient": false
},
"due_amount": {
"currency_code": "USD",
"value": "74.21"
},
"id": "INV2-XFXV-YW42-ZANU-4F33",
"invoicer": {
"additional_notes": "2-4",
"address": {
"address_line_1": "1234 First Street",
"address_line_2": "337673 Hillside Court",
"admin_area_1": "CA",
"admin_area_2": "Anytown",
"country_code": "US",
"postal_code": "98765"
},
"email_address": "merchant@example.com",
"logo_url": "https://example.com/logo.PNG",
"name": {
"full_name": "David Larusso",
"given_name": "David",
"surname": "Larusso"
},
"phones": [
{
"country_code": "001",
"national_number": "4085551234",
"phone_type": "MOBILE"
}
],
"tax_id": "ABcNkWSfb5ICTt73nD3QON1fnnpgNKBy- Jb5SeuGj185MNNw6g",
"website": "www.test.com"
},
"items": [
{
"description": "Elastic mat to practice yoga.",
"discount": {
"amount": {
"currency_code": "USD",
"value": "-2.50"
},
"percent": "5"
},
"id": "ITEM-5335764681676603X",
"name": "Yoga Mat",
"quantity": "1",
"tax": {
"amount": {
"currency_code": "USD",
"value": "3.27"
},
"id": "TAX-5XV24702TP4910056",
"name": "Sales Tax",
"percent": "7.25"
},
"unit_amount": {
"currency_code": "USD",
"value": "50.00"
},
"unit_of_measure": "QUANTITY"
},
{
"discount": {
"amount": {
"currency_code": "USD",
"value": "-5.00"
}
},
"id": "ITEM-1B467958Y9218273X",
"name": "Yoga t-shirt",
"quantity": "1",
"tax": {
"amount": {
"currency_code": "USD",
"value": "0.34"
},
"id": "TAX-5XV24702TP4910056",
"name": "Sales Tax",
"percent": "7.25"
},
"unit_amount": {
"currency_code": "USD",
"value": "10.00"
},
"unit_of_measure": "QUANTITY"
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33",
"method": "GET",
"rel": "self"
},
{
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33/send",
"method": "POST",
"rel": "send"
},
{
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33",
"method": "PUT",
"rel": "replace"
},
{
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33",
"method": "DELETE",
"rel": "delete"
},
{
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33/payments",
"method": "POST",
"rel": "record-payment"
}
],
"primary_recipients": [
{
"billing_info": {
"address": {
"address_line_1": "1234 Main Street",
"admin_area_1": "CA",
"admin_area_2": "Anytown",
"country_code": "US",
"postal_code": "98765"
},
"email_address": "bill-me@example.com",
"name": {
"full_name": "Stephanie Meyers",
"given_name": "Stephanie",
"surname": "Meyers"
}
},
"shipping_info": {
"address": {
"address_line_1": "1234 Main Street",
"admin_area_1": "CA",
"admin_area_2": "Anytown",
"country_code": "US",
"postal_code": "98765"
},
"name": {
"full_name": "Stephanie Meyers",
"given_name": "Stephanie",
"surname": "Meyers"
}
}
}
],
"status": "DRAFT",
"unilateral": false
}
`)
var testInvoice paypal.Invoice
err := json.Unmarshal(testInvoiceJSONData, &testInvoice)
assert.Equal(t, nil, err) // if passed, means unmarshalling was successful
ctx := context.Background()
c, _ := paypal.NewClient(devTestClientID, devTestSecret, devAPIBaseSandBox)
_, _ = c.GetAccessToken(ctx)
invoice, err := c.GetInvoiceDetails(ctx, "INV2-XFXV-YW42-ZANU-4F33")
assert.Equal(t, nil, err) // if passed, means that request was successful
assertTwoInvoices(t, *invoice, testInvoice)
}

179
order.go
View File

@ -1,17 +1,13 @@
package paypal
package paypalsdk
import (
"context"
"encoding/json"
"fmt"
)
import "fmt"
// GetOrder retrieves order by ID
// Endpoint: GET /v2/checkout/orders/ID
func (c *Client) GetOrder(ctx context.Context, orderID string) (*Order, error) {
// Endpoint: GET /v1/payments/orders/ID
func (c *Client) GetOrder(orderID string) (*Order, error) {
order := &Order{}
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), nil)
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payments/orders/", orderID), nil)
if err != nil {
return order, err
}
@ -23,79 +19,16 @@ func (c *Client) GetOrder(ctx context.Context, orderID string) (*Order, error) {
return order, nil
}
// Create an order
// Endpoint: POST /v2/checkout/orders
func (c *Client) CreateOrder(ctx context.Context, intent string, purchaseUnits []PurchaseUnitRequest, paymentSource *PaymentSource, appContext *ApplicationContext) (*Order, error) {
return c.CreateOrderWithPaypalRequestID(ctx, intent, purchaseUnits, paymentSource, appContext, "")
}
// CreateOrderWithPaypalRequestID - Use this call to create an order with idempotency
// Endpoint: POST /v2/checkout/orders
func (c *Client) CreateOrderWithPaypalRequestID(ctx context.Context,
intent string,
purchaseUnits []PurchaseUnitRequest,
paymentSource *PaymentSource,
appContext *ApplicationContext,
requestID string,
) (*Order, error) {
type createOrderRequest struct {
Intent string `json:"intent"`
PaymentSource *PaymentSource `json:"payment_source,omitempty"`
PurchaseUnits []PurchaseUnitRequest `json:"purchase_units"`
ApplicationContext *ApplicationContext `json:"application_context,omitempty"`
// AuthorizeOrder - Use this call to authorize an order.
// Endpoint: POST /v1/payments/orders/ID/authorize
func (c *Client) AuthorizeOrder(orderID string, amount *Amount) (*Authorization, error) {
type authRequest struct {
Amount *Amount `json:"amount"`
}
order := &Order{}
auth := &Authorization{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders"), createOrderRequest{Intent: intent, PurchaseUnits: purchaseUnits, PaymentSource: paymentSource, ApplicationContext: appContext})
if err != nil {
return order, err
}
if requestID != "" {
req.Header.Set("PayPal-Request-Id", requestID)
}
if err = c.SendWithAuth(req, order); err != nil {
return order, err
}
return order, nil
}
// UpdateOrder updates the order by ID
// Endpoint: PATCH /v2/checkout/orders/ID
func (c *Client) UpdateOrder(ctx context.Context, orderID string, op string, path string, value map[string]string) error {
type patchRequest struct {
Op string `json:"op"`
Path string `json:"path"`
Value map[string]string `json:"value"`
}
req, err := c.NewRequest(ctx, "PATCH", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), []patchRequest{
{
Op: op,
Path: path,
Value: value,
},
})
if err != nil {
return err
}
if err = c.SendWithAuth(req, nil); err != nil {
return err
}
return nil
}
// AuthorizeOrder - https://developer.paypal.com/docs/api/orders/v2/#orders_authorize
// Endpoint: POST /v2/checkout/orders/ID/authorize
func (c *Client) AuthorizeOrder(ctx context.Context, orderID string, authorizeOrderRequest AuthorizeOrderRequest) (*AuthorizeOrderResponse, error) {
auth := &AuthorizeOrderResponse{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/authorize"), authorizeOrderRequest)
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/orders/"+orderID+"/authorize"), authRequest{Amount: amount})
if err != nil {
return auth, err
}
@ -107,42 +40,22 @@ func (c *Client) AuthorizeOrder(ctx context.Context, orderID string, authorizeOr
return auth, nil
}
// CaptureOrder - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
// Endpoint: POST /v2/checkout/orders/ID/capture
func (c *Client) CaptureOrder(ctx context.Context, orderID string, captureOrderRequest CaptureOrderRequest) (*CaptureOrderResponse, error) {
return c.CaptureOrderWithPaypalRequestId(ctx, orderID, captureOrderRequest, "", nil)
}
// CaptureOrder - Use this call to capture a payment on an order. To use this call, an original payment call must specify an intent of order.
// Endpoint: POST /v1/payments/orders/ID/capture
func (c *Client) CaptureOrder(orderID string, amount *Amount, isFinalCapture bool, currency *Currency) (*Capture, error) {
type captureRequest struct {
Amount *Amount `json:"amount"`
IsFinalCapture bool `json:"is_final_capture"`
Currency *Currency `json:"transaction_fee"`
}
// CaptureOrder with idempotency - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
// Endpoint: POST /v2/checkout/orders/ID/capture
// https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
func (c *Client) CaptureOrderWithPaypalRequestId(ctx context.Context,
orderID string,
captureOrderRequest CaptureOrderRequest,
requestID string,
mockResponse *CaptureOrderMockResponse,
) (*CaptureOrderResponse, error) {
capture := &CaptureOrderResponse{}
capture := &Capture{}
c.SetReturnRepresentation()
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/capture"), captureOrderRequest)
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/orders/"+orderID+"/capture"), captureRequest{Amount: amount, IsFinalCapture: isFinalCapture, Currency: currency})
if err != nil {
return capture, err
}
if requestID != "" {
req.Header.Set("PayPal-Request-Id", requestID)
}
if mockResponse != nil {
mock, err := json.Marshal(mockResponse)
if err != nil {
return nil, err
}
req.Header.Set("PayPal-Mock-Response", string(mock))
}
if err = c.SendWithAuth(req, capture); err != nil {
return capture, err
}
@ -150,48 +63,20 @@ func (c *Client) CaptureOrderWithPaypalRequestId(ctx context.Context,
return capture, nil
}
// RefundCapture - https://developer.paypal.com/docs/api/payments/v2/#captures_refund
// Endpoint: POST /v2/payments/captures/ID/refund
func (c *Client) RefundCapture(ctx context.Context, captureID string, refundCaptureRequest RefundCaptureRequest) (*RefundResponse, error) {
return c.RefundCaptureWithPaypalRequestId(ctx, captureID, refundCaptureRequest, "")
}
// VoidOrder - Use this call to void an existing order.
// Note: An order cannot be voided if payment has already been partially or fully captured.
// Endpoint: POST /v1/payments/orders/ID/do-void
func (c *Client) VoidOrder(orderID string) (*Order, error) {
order := &Order{}
// RefundCapture with idempotency - https://developer.paypal.com/docs/api/payments/v2/#captures_refund
// Endpoint: POST /v2/payments/captures/ID/refund
func (c *Client) RefundCaptureWithPaypalRequestId(ctx context.Context,
captureID string,
refundCaptureRequest RefundCaptureRequest,
requestID string,
) (*RefundResponse, error) {
refund := &RefundResponse{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/captures/"+captureID+"/refund"), refundCaptureRequest)
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/orders/"+orderID+"/do-void"), nil)
if err != nil {
return refund, err
return order, err
}
if requestID != "" {
req.Header.Set("PayPal-Request-Id", requestID)
if err = c.SendWithAuth(req, order); err != nil {
return order, err
}
if err = c.SendWithAuth(req, refund); err != nil {
return refund, err
}
return refund, nil
}
// CapturedDetail - https://developer.paypal.com/docs/api/payments/v2/#captures_get
// Endpoint: GET /v2/payments/captures/ID
func (c *Client) CapturedDetail(ctx context.Context, captureID string) (*CaptureDetailsResponse, error) {
response := &CaptureDetailsResponse{}
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/captures/"+captureID), nil)
if err != nil {
return response, err
}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
return response, nil
return order, nil
}

146
payment.go Normal file
View File

@ -0,0 +1,146 @@
package paypalsdk
import (
"bytes"
"errors"
"fmt"
"net/http"
)
// ListPaymentsResp slice of payments
type ListPaymentsResp struct {
Payments []Payment `json:"payments"`
}
// CreatePaymentResp contains Payment Info and Links slice
type CreatePaymentResp struct {
*Payment
Links []Link `json:"links"`
}
// CreateDirectPaypalPayment sends request to create a payment with payment_method=paypal
// CreatePayment is more common function for any kind of payment
// Endpoint: POST /v1/payments/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 +
"\",\"currency\":\"" + amount.Currency + "\"},\"description\":\"" + description + "\"}],\"redirect_urls\":{\"return_url\":\"" +
redirectURI + "\",\"cancel_url\":\"" + cancelURI + "\"}}"))
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment"), buf)
if err != nil {
return &PaymentResponse{}, err
}
req.SetBasicAuth(c.ClientID, c.Secret)
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
p := PaymentResponse{}
if err = c.SendWithAuth(req, &p); err != nil {
return &p, err
}
if p.ID == "" {
return &p, errors.New("Unable to create payment with this access token")
}
return &p, err
}
// CreatePayment creates a payment in Paypal
// Depending on the payment_method and the funding_instrument, you can use the payment resource for direct credit card payments, stored credit card payments, or PayPal account payments.
// Endpoint: POST /v1/payments/payment
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{}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
return response, nil
}
// ExecuteApprovedPayment - Use this call to execute (complete) a PayPal payment that has been approved by the payer. You can optionally update transaction information when executing the payment by passing in one or more transactions.
// Endpoint: POST /v1/payments/payment/paymentID/execute
func (c *Client) ExecuteApprovedPayment(paymentID string, payerID string) (*ExecuteResponse, error) {
buf := bytes.NewBuffer([]byte("{\"payer_id\":\"" + payerID + "\"}"))
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment/"+paymentID+"/execute"), buf)
if err != nil {
return &ExecuteResponse{}, err
}
req.SetBasicAuth(c.ClientID, c.Secret)
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
e := ExecuteResponse{}
if err = c.SendWithAuth(req, &e); err != nil {
return &e, err
}
if e.ID == "" {
return &e, errors.New("Unable to execute payment with paymentID=" + paymentID)
}
return &e, err
}
// GetPayment gets a payment from PayPal
// Endpoint: GET /v1/payments/payment/ID
func (c *Client) GetPayment(paymentID string) (*Payment, error) {
p := Payment{}
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment/"+paymentID), nil)
if err != nil {
return &p, err
}
if err = c.SendWithAuth(req, &p); err != nil {
return &p, err
}
if p.ID == "" {
return &p, errors.New("Unable to get payment with paymentID=" + paymentID)
}
return &p, nil
}
// PatchPayment modifies some fields of a payment prior to execution
// Endpoint: PATCH /v1/payments/payment/ID
func (c *Client) PatchPayment(paymentID string, p []PaymentPatch) (*Payment, error) {
req, err := c.NewRequest("PATCH", fmt.Sprintf("%s/v1/payments/payment/%s", c.APIBase, paymentID), p)
if err != nil {
return nil, err
}
response := Payment{}
if err = c.SendWithAuth(req, &response); err != nil {
return nil, err
}
return &response, nil
}
// GetPayments retrieve payments resources from Paypal
// Endpoint: GET /v1/payments/payment/
func (c *Client) GetPayments() ([]Payment, error) {
var p ListPaymentsResp
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment/"), nil)
if err != nil {
return p.Payments, err
}
if err = c.SendWithAuth(req, &p); err != nil {
return p.Payments, err
}
return p.Payments, nil
}

View File

@ -1,21 +1,20 @@
package paypal
package paypalsdk
import (
"context"
"fmt"
)
// CreatePayout submits a payout with an asynchronous API call, which immediately returns the results of a PayPal payment.
// CreateSinglePayout submits a payout with an asynchronous API call, which immediately returns the results of a PayPal payment.
// For email payout set RecipientType: "EMAIL" and receiver email into Receiver
// Endpoint: POST /v1/payments/payouts
func (c *Client) CreatePayout(ctx context.Context, p Payout) (*PayoutResponse, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts"), p)
response := &PayoutResponse{}
func (c *Client) CreateSinglePayout(p Payout) (*PayoutResponse, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts"), p)
if err != nil {
return response, err
return &PayoutResponse{}, err
}
response := &PayoutResponse{}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
@ -26,14 +25,15 @@ func (c *Client) CreatePayout(ctx context.Context, p Payout) (*PayoutResponse, e
// GetPayout shows the latest status of a batch payout along with the transaction status and other data for individual items.
// Also, returns IDs for the individual payout items. You can use these item IDs in other calls.
// Endpoint: GET /v1/payments/payouts/ID
func (c *Client) GetPayout(ctx context.Context, payoutBatchID string) (*PayoutResponse, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts/"+payoutBatchID), nil)
response := &PayoutResponse{}
func (c *Client) GetPayout(payoutBatchID string) (*PayoutResponse, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts/"+payoutBatchID), nil)
if err != nil {
return response, err
return &PayoutResponse{}, err
}
response := &PayoutResponse{}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
@ -44,14 +44,15 @@ func (c *Client) GetPayout(ctx context.Context, payoutBatchID string) (*PayoutRe
// GetPayoutItem shows the details for a payout item.
// Use this call to review the current status of a previously unclaimed, or pending, payout item.
// Endpoint: GET /v1/payments/payouts-item/ID
func (c *Client) GetPayoutItem(ctx context.Context, payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID), nil)
response := &PayoutItemResponse{}
func (c *Client) GetPayoutItem(payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID), nil)
if err != nil {
return response, err
return &PayoutItemResponse{}, err
}
response := &PayoutItemResponse{}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
@ -62,14 +63,15 @@ func (c *Client) GetPayoutItem(ctx context.Context, payoutItemID string) (*Payou
// CancelPayoutItem cancels an unclaimed Payout Item. If no one claims the unclaimed item within 30 days,
// the funds are automatically returned to the sender. Use this call to cancel the unclaimed item before the automatic 30-day refund.
// Endpoint: POST /v1/payments/payouts-item/ID/cancel
func (c *Client) CancelPayoutItem(ctx context.Context, payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID+"/cancel"), nil)
response := &PayoutItemResponse{}
func (c *Client) CancelPayoutItem(payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID+"/cancel"), nil)
if err != nil {
return response, err
return &PayoutItemResponse{}, err
}
response := &PayoutItemResponse{}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}

View File

@ -1,119 +0,0 @@
package paypal
import (
"context"
"fmt"
"net/http"
)
type (
// Product struct
Product struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Category ProductCategory `json:"category,omitempty"`
Type ProductType `json:"type"`
ImageUrl string `json:"image_url,omitempty"`
HomeUrl string `json:"home_url,omitempty"`
}
CreateProductResponse struct {
Product
SharedResponse
}
ListProductsResponse struct {
Products []Product `json:"products"`
SharedListResponse
}
ProductListParameters struct {
ListParams
}
)
func (p *Product) GetUpdatePatch() []Patch {
return []Patch{
{
Operation: "replace",
Path: "/description",
Value: p.Description,
},
{
Operation: "replace",
Path: "/category",
Value: p.Category,
},
{
Operation: "replace",
Path: "/image_url",
Value: p.ImageUrl,
},
{
Operation: "replace",
Path: "/home_url",
Value: p.HomeUrl,
},
}
}
// CreateProduct creates a product
// Doc: https://developer.paypal.com/docs/api/catalog-products/v1/#products_create
// Endpoint: POST /v1/catalogs/products
func (c *Client) CreateProduct(ctx context.Context, product Product) (*CreateProductResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/catalogs/products"), product)
response := &CreateProductResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// UpdateProduct. updates a product information
// Doc: https://developer.paypal.com/docs/api/catalog-products/v1/#products_patch
// Endpoint: PATCH /v1/catalogs/products/:product_id
func (c *Client) UpdateProduct(ctx context.Context, product Product) error {
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/catalogs/products/", product.ID), product.GetUpdatePatch())
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Get product details
// Doc: https://developer.paypal.com/docs/api/catalog-products/v1/#products_get
// Endpoint: GET /v1/catalogs/products/:product_id
func (c *Client) GetProduct(ctx context.Context, productId string) (*Product, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/catalogs/products/", productId), nil)
response := &Product{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// List all products
// Doc: https://developer.paypal.com/docs/api/catalog-products/v1/#products_list
// Endpoint: GET /v1/catalogs/products
func (c *Client) ListProducts(ctx context.Context, params *ProductListParameters) (*ListProductsResponse, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/catalogs/products"), nil)
response := &ListProductsResponse{}
if err != nil {
return response, err
}
if params != nil {
q := req.URL.Query()
q.Add("page", params.Page)
q.Add("page_size", params.PageSize)
q.Add("total_required", params.TotalRequired)
req.URL.RawQuery = q.Encode()
}
err = c.SendWithAuth(req, response)
return response, err
}

58
sale.go
View File

@ -1,17 +1,55 @@
package paypal
package paypalsdk
import (
"context"
"fmt"
)
import "fmt"
// GetSale returns a sale by ID
// Use this call to get details about a sale transaction.
// Note: This call returns only the sales that were created via the REST API.
// Endpoint: GET /v1/payments/sale/ID
func (c *Client) GetSale(saleID string) (*Sale, error) {
sale := &Sale{}
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/sale/"+saleID), nil)
if err != nil {
return sale, err
}
if err = c.SendWithAuth(req, sale); err != nil {
return sale, err
}
return sale, nil
}
// RefundSale refunds a completed payment.
// Use this call to refund a completed payment. Provide the sale_id in the URI and an empty JSON payload for a full refund. For partial refunds, you can include an amount.
// Endpoint: POST /v1/payments/sale/ID/refund
func (c *Client) RefundSale(saleID string, a *Amount) (*Refund, error) {
type refundRequest struct {
Amount *Amount `json:"amount"`
}
// GetRefund by ID
// Use it to look up details of a specific refund on direct and captured payments.
// Endpoint: GET /v2/payments/refund/ID
func (c *Client) GetRefund(ctx context.Context, refundID string) (*Refund, error) {
refund := &Refund{}
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/refund/"+refundID), nil)
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/sale/"+saleID+"/refund"), &refundRequest{Amount: a})
if err != nil {
return refund, err
}
if err = c.SendWithAuth(req, refund); err != nil {
return refund, err
}
return refund, nil
}
// GetRefund by ID
// Use it to look up details of a specific refund on direct and captured payments.
// Endpoint: GET /v1/payments/refund/ID
func (c *Client) GetRefund(refundID string) (*Refund, error) {
refund := &Refund{}
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/refund/"+refundID), nil)
if err != nil {
return refund, err
}

View File

@ -1,234 +0,0 @@
package paypal
import (
"context"
"fmt"
"net/http"
"time"
)
type (
SubscriptionBase struct {
PlanID string `json:"plan_id"`
StartTime *JSONTime `json:"start_time,omitempty"`
Quantity string `json:"quantity,omitempty"`
ShippingAmount *Money `json:"shipping_amount,omitempty"`
Subscriber *Subscriber `json:"subscriber,omitempty"`
AutoRenewal bool `json:"auto_renewal,omitempty"`
ApplicationContext *ApplicationContext `json:"application_context,omitempty"`
CustomID string `json:"custom_id,omitempty"`
Plan *PlanOverride `json:"plan,omitempty"`
}
SubscriptionDetails struct {
ID string `json:"id,omitempty"`
SubscriptionStatus SubscriptionStatus `json:"status,omitempty"`
SubscriptionStatusChangeNote string `json:"status_change_note,omitempty"`
StatusUpdateTime time.Time `json:"status_update_time,omitempty"`
}
Subscription struct {
SubscriptionDetailResp
}
// SubscriptionDetailResp struct
SubscriptionDetailResp struct {
SubscriptionBase
SubscriptionDetails
BillingInfo BillingInfo `json:"billing_info,omitempty"` // not found in documentation
SharedResponse
}
SubscriptionCaptureResponse struct {
Status SubscriptionTransactionStatus `json:"status"`
Id string `json:"id"`
AmountWithBreakdown AmountWithBreakdown `json:"amount_with_breakdown"`
PayerName Name `json:"payer_name"`
PayerEmail string `json:"payer_email"`
Time time.Time `json:"time"`
}
//Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-amount_with_breakdown
AmountWithBreakdown struct {
GrossAmount Money `json:"gross_amount"`
FeeAmount Money `json:"fee_amount"`
ShippingAmount Money `json:"shipping_amount"`
TaxAmount Money `json:"tax_amount"`
NetAmount Money `json:"net_amount"`
}
SubscriptionTransactionsParams struct {
SubscriptionId string
StartTime time.Time
EndTime time.Time
}
SubscriptionTransactionsResponse struct {
Transactions []SubscriptionCaptureResponse `json:"transactions"`
SharedListResponse
}
CaptureRequest struct {
Note string `json:"note"`
CaptureType CaptureType `json:"capture_type"`
Amount Money `json:"amount"`
}
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-plan_override
PlanOverride struct {
BillingCycles []BillingCycleOverride `json:"billing_cycles,omitempty"`
PaymentPreferences *PaymentPreferencesOverride `json:"payment_preferences,omitempty"`
Taxes *TaxesOverride `json:"taxes,omitempty"`
}
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-payment_preferences_override
PaymentPreferencesOverride struct {
AutoBillOutstanding bool `json:"auto_bill_outstanding,omitempty"`
SetupFee Money `json:"setup_fee,omitempty"`
SetupFeeFailureAction SetupFeeFailureAction `json:"setup_fee_failure_action,omitempty"`
PaymentFailureThreshold int `json:"payment_failure_threshold,omitempty"`
}
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-payment_preferences_override
TaxesOverride struct {
Percentage string `json:"percentage,omitempty"`
Inclusive *bool `json:"inclusive,omitempty"`
}
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-billing_cycle_override
BillingCycleOverride struct {
PricingScheme PricingScheme `json:"pricing_scheme,omitempty"`
Sequence *int `json:"sequence,omitempty"`
TotalCycles *int `json:"total_cycles,omitempty"`
}
)
func (p *Subscription) GetUpdatePatch() []Patch {
result := []Patch{
{
Operation: "replace",
Path: "/billing_info/outstanding_balance",
Value: p.BillingInfo.OutstandingBalance,
},
}
return result
}
// CreateSubscriptionPlan creates a subscriptionPlan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
// Endpoint: POST /v1/billing/subscriptions
func (c *Client) CreateSubscription(ctx context.Context, newSubscription SubscriptionBase) (*SubscriptionDetailResp, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/billing/subscriptions"), newSubscription)
req.Header.Add("Prefer", "return=representation")
response := &SubscriptionDetailResp{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// UpdateSubscriptionPlan. updates a plan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_patch
// Endpoint: PATCH /v1/billing/subscriptions/:subscription_id
func (c *Client) UpdateSubscription(ctx context.Context, updatedSubscription Subscription) error {
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/billing/subscriptions/", updatedSubscription.ID), updatedSubscription.GetUpdatePatch())
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// GetSubscriptionDetails shows details for a subscription, by ID.
// Endpoint: GET /v1/billing/subscriptions/
func (c *Client) GetSubscriptionDetails(ctx context.Context, subscriptionID string) (*SubscriptionDetailResp, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/billing/subscriptions/%s", c.APIBase, subscriptionID), nil)
response := &SubscriptionDetailResp{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// Activates the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_activate
// Endpoint: POST /v1/billing/subscriptions/{id}/activate
func (c *Client) ActivateSubscription(ctx context.Context, subscriptionId, activateReason string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/activate", c.APIBase, subscriptionId), map[string]string{"reason": activateReason})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Cancels the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
// Endpoint: POST /v1/billing/subscriptions/{id}/cancel
func (c *Client) CancelSubscription(ctx context.Context, subscriptionId, cancelReason string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/cancel", c.APIBase, subscriptionId), map[string]string{"reason": cancelReason})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Captures an authorized payment from the subscriber on the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_capture
// Endpoint: POST /v1/billing/subscriptions/{id}/capture
func (c *Client) CaptureSubscription(ctx context.Context, subscriptionId string, request CaptureRequest) (*SubscriptionCaptureResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/capture", c.APIBase, subscriptionId), request)
response := &SubscriptionCaptureResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// Suspends the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_suspend
// Endpoint: POST /v1/billing/subscriptions/{id}/suspend
func (c *Client) SuspendSubscription(ctx context.Context, subscriptionId, reason string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/suspend", c.APIBase, subscriptionId), map[string]string{"reason": reason})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Lists transactions for a subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_transactions
// Endpoint: GET /v1/billing/subscriptions/{id}/transactions
func (c *Client) GetSubscriptionTransactions(ctx context.Context, requestParams SubscriptionTransactionsParams) (*SubscriptionTransactionsResponse, error) {
startTime := requestParams.StartTime.Format("2006-01-02T15:04:05Z")
endTime := requestParams.EndTime.Format("2006-01-02T15:04:05Z")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/billing/subscriptions/%s/transactions?start_time=%s&end_time=%s", c.APIBase, requestParams.SubscriptionId, startTime, endTime), nil)
response := &SubscriptionTransactionsResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// Revise plan or quantity of subscription
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_revise
// Endpoint: POST /v1/billing/subscriptions/{id}/revise
func (c *Client) ReviseSubscription(ctx context.Context, subscriptionId string, reviseSubscription SubscriptionBase) (*SubscriptionDetailResp, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/revise", c.APIBase, subscriptionId), reviseSubscription)
response := &SubscriptionDetailResp{}
if err != nil {
return response, err
}
req.Header.Add("Content-Type", "application/json")
err = c.SendWithAuth(req, response)
return response, err
}

View File

@ -1,233 +0,0 @@
package paypal
import (
"context"
"fmt"
"net/http"
"time"
)
type (
// SubscriptionDetailResp struct
SubscriptionPlan struct {
ID string `json:"id,omitempty"`
ProductId string `json:"product_id"`
Name string `json:"name"`
Status SubscriptionPlanStatus `json:"status"`
Description string `json:"description,omitempty"`
BillingCycles []BillingCycle `json:"billing_cycles"`
PaymentPreferences *PaymentPreferences `json:"payment_preferences"`
Taxes *Taxes `json:"taxes"`
QuantitySupported bool `json:"quantity_supported"` //Indicates whether you can subscribe to this plan by providing a quantity for the goods or service.
}
// Doc https://developer.paypal.com/docs/api/subscriptions/v1/#definition-billing_cycle
BillingCycle struct {
PricingScheme PricingScheme `json:"pricing_scheme"` // The active pricing scheme for this billing cycle. A free trial billing cycle does not require a pricing scheme.
Frequency Frequency `json:"frequency"` // The frequency details for this billing cycle.
TenureType TenureType `json:"tenure_type"` // The tenure type of the billing cycle. In case of a plan having trial cycle, only 2 trial cycles are allowed per plan. The possible values are:
Sequence int `json:"sequence"` // The order in which this cycle is to run among other billing cycles. For example, a trial billing cycle has a sequence of 1 while a regular billing cycle has a sequence of 2, so that trial cycle runs before the regular cycle.
TotalCycles int `json:"total_cycles"` // The number of times this billing cycle gets executed. Trial billing cycles can only be executed a finite number of times (value between 1 and 999 for total_cycles). Regular billing cycles can be executed infinite times (value of 0 for total_cycles) or a finite number of times (value between 1 and 999 for total_cycles).
}
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-payment_preferences
PaymentPreferences struct {
AutoBillOutstanding bool `json:"auto_bill_outstanding"`
SetupFee *Money `json:"setup_fee"`
SetupFeeFailureAction SetupFeeFailureAction `json:"setup_fee_failure_action"`
PaymentFailureThreshold int `json:"payment_failure_threshold"`
}
PricingScheme struct {
Version int `json:"version"`
FixedPrice Money `json:"fixed_price"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
PricingSchemeUpdateRequest struct {
Schemes []PricingSchemeUpdate `json:"pricing_schemes"`
}
PricingSchemeUpdate struct {
BillingCycleSequence int `json:"billing_cycle_sequence"`
PricingScheme PricingScheme `json:"pricing_scheme"`
}
//doc: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-frequency
Frequency struct {
IntervalUnit IntervalUnit `json:"interval_unit"`
IntervalCount int `json:"interval_count"` //different per unit. check documentation
}
Taxes struct {
Percentage string `json:"percentage"`
Inclusive bool `json:"inclusive"`
}
CreateSubscriptionPlanResponse struct {
SubscriptionPlan
SharedResponse
}
SubscriptionPlanListParameters struct {
ProductId string `json:"product_id"`
PlanIds string `json:"plan_ids"` // Filters the response by list of plan IDs. Filter supports upto 10 plan IDs.
ListParams
}
ListSubscriptionPlansResponse struct {
Plans []SubscriptionPlan `json:"plans"`
SharedListResponse
}
)
func (p *SubscriptionPlan) GetUpdatePatch() []Patch {
result := []Patch{
{
Operation: "replace",
Path: "/description",
Value: p.Description,
},
}
if p.Taxes != nil {
result = append(result, Patch{
Operation: "replace",
Path: "/taxes/percentage",
Value: p.Taxes.Percentage,
})
}
if p.PaymentPreferences != nil {
if p.PaymentPreferences.SetupFee != nil {
result = append(result, Patch{
Operation: "replace",
Path: "/payment_preferences/setup_fee",
Value: p.PaymentPreferences.SetupFee,
},
)
}
result = append(result, []Patch{{
Operation: "replace",
Path: "/payment_preferences/auto_bill_outstanding",
Value: p.PaymentPreferences.AutoBillOutstanding,
},
{
Operation: "replace",
Path: "/payment_preferences/payment_failure_threshold",
Value: p.PaymentPreferences.PaymentFailureThreshold,
},
{
Operation: "replace",
Path: "/payment_preferences/setup_fee_failure_action",
Value: p.PaymentPreferences.SetupFeeFailureAction,
}}...)
}
return result
}
// CreateSubscriptionPlan creates a subscriptionPlan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_create
// Endpoint: POST /v1/billing/plans
func (c *Client) CreateSubscriptionPlan(ctx context.Context, newPlan SubscriptionPlan) (*CreateSubscriptionPlanResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/billing/plans"), newPlan)
response := &CreateSubscriptionPlanResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// UpdateSubscriptionPlan. updates a plan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_patch
// Endpoint: PATCH /v1/billing/plans/:plan_id
func (c *Client) UpdateSubscriptionPlan(ctx context.Context, updatedPlan SubscriptionPlan) error {
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/billing/plans/", updatedPlan.ID), updatedPlan.GetUpdatePatch())
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// UpdateSubscriptionPlan. updates a plan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_get
// Endpoint: GET /v1/billing/plans/:plan_id
func (c *Client) GetSubscriptionPlan(ctx context.Context, planId string) (*SubscriptionPlan, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/billing/plans/", planId), nil)
response := &SubscriptionPlan{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// List all plans
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_list
// Endpoint: GET /v1/billing/plans
func (c *Client) ListSubscriptionPlans(ctx context.Context, params *SubscriptionPlanListParameters) (*ListSubscriptionPlansResponse, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/billing/plans"), nil)
response := &ListSubscriptionPlansResponse{}
if err != nil {
return response, err
}
if params != nil {
q := req.URL.Query()
q.Add("page", params.Page)
q.Add("page_size", params.PageSize)
q.Add("total_required", params.TotalRequired)
q.Add("product_id", params.ProductId)
q.Add("plan_ids", params.PlanIds)
req.URL.RawQuery = q.Encode()
}
err = c.SendWithAuth(req, response)
return response, err
}
// Activates a plan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_activate
// Endpoint: POST /v1/billing/plans/{id}/activate
func (c *Client) ActivateSubscriptionPlan(ctx context.Context, planId string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/plans/%s/activate", c.APIBase, planId), nil)
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Deactivates a plan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_deactivate
// Endpoint: POST /v1/billing/plans/{id}/deactivate
func (c *Client) DeactivateSubscriptionPlans(ctx context.Context, planId string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/plans/%s/deactivate", c.APIBase, planId), nil)
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Updates pricing for a plan. For example, you can update a regular billing cycle from $5 per month to $7 per month.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#plans_update-pricing-schemes
// Endpoint: POST /v1/billing/plans/{id}/update-pricing-schemes
func (c *Client) UpdateSubscriptionPlanPricing(ctx context.Context, planId string, pricingSchemes []PricingSchemeUpdate) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/plans/%s/update-pricing-schemes", c.APIBase, planId), PricingSchemeUpdateRequest{
Schemes: pricingSchemes,
})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}

View File

@ -1,96 +0,0 @@
package paypal
import (
"context"
"fmt"
"strconv"
"time"
)
type TransactionSearchRequest struct {
TransactionID *string
TransactionType *string
TransactionStatus *string
TransactionAmount *string
TransactionCurrency *string
StartDate time.Time
EndDate time.Time
PaymentInstrumentType *string
StoreID *string
TerminalID *string
Fields *string
BalanceAffectingRecordsOnly *string
PageSize *int
Page *int
}
type TransactionSearchResponse struct {
TransactionDetails []SearchTransactionDetails `json:"transaction_details"`
AccountNumber string `json:"account_number"`
StartDate JSONTime `json:"start_date"`
EndDate JSONTime `json:"end_date"`
LastRefreshDatetime JSONTime `json:"last_refreshed_datetime"`
Page int `json:"page"`
SharedListResponse
}
// ListTransactions - Use this to search PayPal transactions from the last 31 days.
// Endpoint: GET /v1/reporting/transactions
func (c *Client) ListTransactions(ctx context.Context, req *TransactionSearchRequest) (*TransactionSearchResponse, error) {
response := &TransactionSearchResponse{}
r, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/reporting/transactions"), nil)
if err != nil {
return nil, err
}
q := r.URL.Query()
q.Add("start_date", req.StartDate.Format(time.RFC3339))
q.Add("end_date", req.EndDate.Format(time.RFC3339))
if req.TransactionID != nil {
q.Add("transaction_id", *req.TransactionID)
}
if req.TransactionType != nil {
q.Add("transaction_type", *req.TransactionType)
}
if req.TransactionStatus != nil {
q.Add("transaction_status", *req.TransactionStatus)
}
if req.TransactionAmount != nil {
q.Add("transaction_amount", *req.TransactionAmount)
}
if req.TransactionCurrency != nil {
q.Add("transaction_currency", *req.TransactionCurrency)
}
if req.PaymentInstrumentType != nil {
q.Add("payment_instrument_type", *req.PaymentInstrumentType)
}
if req.StoreID != nil {
q.Add("store_id", *req.StoreID)
}
if req.TerminalID != nil {
q.Add("terminal_id", *req.TerminalID)
}
if req.Fields != nil {
q.Add("fields", *req.Fields)
}
if req.BalanceAffectingRecordsOnly != nil {
q.Add("balance_affecting_records_only", *req.BalanceAffectingRecordsOnly)
}
if req.PageSize != nil {
q.Add("page_size", strconv.Itoa(*req.PageSize))
}
if req.Page != nil {
q.Add("page", strconv.Itoa(*req.Page))
}
r.URL.RawQuery = q.Encode()
if err = c.SendWithAuth(r, response); err != nil {
return nil, err
}
return response, nil
}

1174
types.go

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,17 @@
package paypal
package paypalsdk
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
type webprofileTestServer struct {
t *testing.T
}
func TestNewClient(t *testing.T) {
c, err := NewClient("", "", "")
if err == nil {
@ -50,6 +57,8 @@ func TestTypeUserInfo(t *testing.T) {
func TestTypeItem(t *testing.T) {
response := `{
"name":"Item",
"price":"22.99",
"currency":"GBP",
"quantity":"1"
}`
@ -60,6 +69,8 @@ func TestTypeItem(t *testing.T) {
}
if i.Name != "Item" ||
i.Price != "22.99" ||
i.Currency != "GBP" ||
i.Quantity != "1" {
t.Errorf("Item decoded result is incorrect, Given: %v", i)
}
@ -133,35 +144,6 @@ func TestTypeErrorResponseTwo(t *testing.T) {
}
}
func TestTypeErrorResponseThree(t *testing.T) {
response := `{
"name": "BUSINESS_ERROR",
"debug_id": "[REDACTED]",
"message": "Business error",
"information_link": "https://developer.paypal.com/webapps/developer/docs/api/#BUSINESS_ERROR",
"details": [
{
"name": "TOKEN_NOT_FOUND",
"message": "Not Found: Invalid BA-Token Identifier"
}
]
}`
i := &ErrorResponse{}
err := json.Unmarshal([]byte(response), i)
if err != nil {
t.Errorf("ErrorResponse Unmarshal failed")
}
if i.Name != "BUSINESS_ERROR" ||
i.Message != "Business error" ||
len(i.Details) != 1 ||
i.Details[0].Name != "TOKEN_NOT_FOUND" ||
i.Details[0].Message != "Not Found: Invalid BA-Token Identifier" {
t.Errorf("ErrorResponse decoded result is incorrect, Given: %v", i)
}
}
func TestTypePayoutResponse(t *testing.T) {
response := `{
"batch_header":{
@ -246,134 +228,6 @@ func TestTypePayoutResponse(t *testing.T) {
}
}
func TestOrderUnmarshal(t *testing.T) {
response := `{
"id": "5O190127TN364715T",
"status": "CREATED",
"links": [
{
"href": "https://api.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/checkoutnow?token=5O190127TN364715T",
"rel": "approve",
"method": "GET"
},
{
"href": "https://api.paypal.com/v2/checkout/orders/5O190127TN364715T/capture",
"rel": "capture",
"method": "POST"
}
]
}`
order := &Order{}
err := json.Unmarshal([]byte(response), order)
if err != nil {
t.Errorf("Order Unmarshal failed")
}
if order.ID != "5O190127TN364715T" ||
order.Status != "CREATED" ||
order.Links[0].Href != "https://api.paypal.com/v2/checkout/orders/5O190127TN364715T" {
t.Errorf("Order decoded result is incorrect, Given: %+v", order)
}
}
func TestOrderCompletedUnmarshal(t *testing.T) {
response := `{
"id": "1K412082HD5737736",
"status": "COMPLETED",
"purchase_units": [
{
"reference_id": "default",
"amount": {
"currency_code": "EUR",
"value": "99.99"
},
"payee": {
"email_address": "payee@business.example.com",
"merchant_id": "7DVPP5Q2RZJQY"
},
"custom_id": "123456",
"soft_descriptor": "PAYPAL *TEST STORE",
"shipping": {
"name": {
"full_name": "John Doe"
},
"address": {
"address_line_1": "Address, Country",
"admin_area_2": "Area2",
"admin_area_1": "Area1",
"postal_code": "123456",
"country_code": "US"
}
},
"payments": {
"captures": [
{
"id": "6V864560EH247264J",
"status": "COMPLETED",
"amount": {
"currency_code": "EUR",
"value": "99.99"
},
"final_capture": true,
"custom_id": "123456",
"create_time": "2021-07-27T09:39:17Z",
"update_time": "2021-07-27T09:39:17Z"
}
]
}
}
],
"payer": {
"name": {
"given_name": "John",
"surname": "Doe"
},
"email_address": "payer@personal.example.com",
"payer_id": "7D36CJQ2TUEUU",
"address": {
"address_line_1": "City, Country",
"admin_area_2": "Area2",
"admin_area_1": "Area1",
"postal_code": "123456",
"country_code": "US"
}
},
"create_time": "2021-07-27T09:38:37Z",
"update_time": "2021-07-27T09:39:17Z",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/1K412082HD5737736",
"rel": "self",
"method": "GET"
}
]
}`
order := &Order{}
err := json.Unmarshal([]byte(response), order)
if err != nil {
t.Errorf("Order Unmarshal failed")
}
if order.ID != "1K412082HD5737736" ||
order.Status != "COMPLETED" ||
order.PurchaseUnits[0].Payee.EmailAddress != "payee@business.example.com" ||
order.PurchaseUnits[0].CustomID != "123456" ||
order.PurchaseUnits[0].Shipping.Name.FullName != "John Doe" ||
order.PurchaseUnits[0].Shipping.Address.AdminArea1 != "Area1" ||
order.Payer.Name.GivenName != "John" ||
order.Payer.Address.AddressLine1 != "City, Country" ||
order.Links[0].Href != "https://api.sandbox.paypal.com/v2/checkout/orders/1K412082HD5737736" {
t.Errorf("Order decoded result is incorrect, Given: %+v", order)
}
}
func TestTypePayoutItemResponse(t *testing.T) {
response := `{
"payout_item_id":"9T35G83YA546X",
@ -471,3 +325,389 @@ func TestTypePaymentPatchMarshal(t *testing.T) {
t.Errorf("PaymentPatch response2 is incorrect,\n Given: %+v\n Expected: %+v", string(response2), string(p2expectedresponse))
}
}
// ServeHTTP implements http.Handler
func (ts *webprofileTestServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ts.t.Log(r.RequestURI)
if r.RequestURI == "/v1/payment-experience/web-profiles" {
if r.Method == "POST" {
ts.create(w, r)
}
if r.Method == "GET" {
ts.list(w, r)
}
}
if r.RequestURI == "/v1/payment-experience/web-profiles/XP-CP6S-W9DY-96H8-MVN2" {
if r.Method == "GET" {
ts.getvalid(w, r)
}
if r.Method == "PUT" {
ts.updatevalid(w, r)
}
if r.Method == "DELETE" {
ts.deletevalid(w, r)
}
}
if r.RequestURI == "/v1/payment-experience/web-profiles/foobar" {
if r.Method == "GET" {
ts.getinvalid(w, r)
}
if r.Method == "PUT" {
ts.updateinvalid(w, r)
}
if r.Method == "DELETE" {
ts.deleteinvalid(w, r)
}
}
}
func (ts *webprofileTestServer) create(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
err = json.Unmarshal(body, &data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
var raw map[string]string
w.Header().Set("Content-Type", "application/json")
if name, ok := data["name"]; !ok || name == "" {
raw = map[string]string{
"name": "VALIDATION_ERROR",
"message": "should have name",
}
w.WriteHeader(http.StatusBadRequest)
} else {
raw = map[string]string{
"id": "XP-CP6S-W9DY-96H8-MVN2",
}
w.WriteHeader(http.StatusCreated)
}
res, _ := json.Marshal(raw)
w.Write(res)
}
func (ts *webprofileTestServer) updatevalid(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
err = json.Unmarshal(body, &data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
if ID, ok := data["id"]; !ok || ID != "XP-CP6S-W9DY-96H8-MVN2" {
raw := map[string]string{
"name": "INVALID_RESOURCE_ID",
"message": "id invalid",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
res, _ := json.Marshal(raw)
w.Write(res)
return
}
if name, ok := data["name"]; !ok || name == "" {
raw := map[string]string{
"name": "VALIDATION_ERROR",
"message": "should have name",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
res, _ := json.Marshal(raw)
w.Write(res)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (ts *webprofileTestServer) updateinvalid(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
raw := map[string]interface{}{
"name": "INVALID_RESOURCE_ID",
"message": "foobar not found",
}
res, _ := json.Marshal(raw)
w.Write(res)
}
func (ts *webprofileTestServer) getvalid(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
raw := map[string]interface{}{
"id": "XP-CP6S-W9DY-96H8-MVN2",
"name": "YeowZa! T-Shirt Shop",
"presentation": map[string]interface{}{
"brand_name": "YeowZa! Paypal",
"logo_image": "http://www.yeowza.com",
"locale_code": "US",
},
"input_fields": map[string]interface{}{
"allow_note": true,
"no_shipping": 0,
"address_override": 1,
},
"flow_config": map[string]interface{}{
"landing_page_type": "Billing",
"bank_txn_pending_url": "http://www.yeowza.com",
},
}
res, _ := json.Marshal(raw)
w.Write(res)
}
func (ts *webprofileTestServer) getinvalid(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
raw := map[string]interface{}{
"name": "INVALID_RESOURCE_ID",
"message": "foobar not found",
}
res, _ := json.Marshal(raw)
w.Write(res)
}
func (ts *webprofileTestServer) list(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
raw := []interface{}{
map[string]interface{}{
"id": "XP-CP6S-W9DY-96H8-MVN2",
"name": "YeowZa! T-Shirt Shop",
},
map[string]interface{}{
"id": "XP-96H8-MVN2-CP6S-W9DY",
"name": "Shop T-Shirt YeowZa! ",
},
}
res, _ := json.Marshal(raw)
w.Write(res)
}
func (ts *webprofileTestServer) deleteinvalid(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
raw := map[string]interface{}{
"name": "INVALID_RESOURCE_ID",
"message": "foobar not found",
}
res, _ := json.Marshal(raw)
w.Write(res)
}
func (ts *webprofileTestServer) deletevalid(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNoContent)
}
func TestCreateWebProfile_valid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
wp := WebProfile{
Name: "YeowZa! T-Shirt Shop",
Presentation: Presentation{
BrandName: "YeowZa! Paypal",
LogoImage: "http://www.yeowza.com",
LocaleCode: "US",
},
InputFields: InputFields{
AllowNote: true,
NoShipping: NoShippingDisplay,
AddressOverride: AddrOverrideFromCall,
},
FlowConfig: FlowConfig{
LandingPageType: LandingPageTypeBilling,
BankTXNPendingURL: "http://www.yeowza.com",
},
}
res, err := c.CreateWebProfile(wp)
if err != nil {
t.Fatal(err)
}
if res.ID != "XP-CP6S-W9DY-96H8-MVN2" {
t.Fatalf("expecting response to have ID = `XP-CP6S-W9DY-96H8-MVN2` got `%s`", res.ID)
}
}
func TestCreateWebProfile_invalid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
wp := WebProfile{}
_, err := c.CreateWebProfile(wp)
if err == nil {
t.Fatalf("expecting an error got nil")
}
}
func TestGetWebProfile_valid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
res, err := c.GetWebProfile("XP-CP6S-W9DY-96H8-MVN2")
if err != nil {
t.Fatal(err)
}
if res.ID != "XP-CP6S-W9DY-96H8-MVN2" {
t.Fatalf("expecting res.ID to have value = `XP-CP6S-W9DY-96H8-MVN2` but got `%s`", res.ID)
}
if res.Name != "YeowZa! T-Shirt Shop" {
t.Fatalf("expecting res.Name to have value = `YeowZa! T-Shirt Shop` but got `%s`", res.Name)
}
}
func TestGetWebProfile_invalid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
_, err := c.GetWebProfile("foobar")
if err == nil {
t.Fatalf("expecting an error got nil")
}
}
func TestGetWebProfiles(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
res, err := c.GetWebProfiles()
if err != nil {
t.Fatal(err)
}
if len(res) != 2 {
t.Fatalf("expecting two results got %d", len(res))
}
}
func TestSetWebProfile_valid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
wp := WebProfile{
ID: "XP-CP6S-W9DY-96H8-MVN2",
Name: "Shop T-Shirt YeowZa!",
}
err := c.SetWebProfile(wp)
if err != nil {
t.Fatal(err)
}
}
func TestSetWebProfile_invalid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
wp := WebProfile{
ID: "foobar",
}
err := c.SetWebProfile(wp)
if err == nil {
t.Fatal(err)
}
wp = WebProfile{}
err = c.SetWebProfile(wp)
if err == nil {
t.Fatal(err)
}
}
func TestDeleteWebProfile_valid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
wp := WebProfile{
ID: "XP-CP6S-W9DY-96H8-MVN2",
Name: "Shop T-Shirt YeowZa!",
}
err := c.SetWebProfile(wp)
if err != nil {
t.Fatal(err)
}
}
func TestDeleteWebProfile_invalid(t *testing.T) {
ts := httptest.NewServer(&webprofileTestServer{t: t})
defer ts.Close()
c, _ := NewClient("foo", "bar", ts.URL)
err := c.DeleteWebProfile("foobar")
if err == nil {
t.Fatal(err)
}
}

View File

@ -1,31 +1,30 @@
package paypal
package paypalsdk
import (
"context"
"fmt"
)
// StoreCreditCard func
// Endpoint: POST /v1/vault/credit-cards
func (c *Client) StoreCreditCard(ctx context.Context, cc CreditCard) (*CreditCard, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/vault/credit-cards"), cc)
func (c *Client) StoreCreditCard(cc CreditCard) (*CreditCard, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/vault/credit-cards"), cc)
if err != nil {
return nil, err
}
response := &CreditCard{}
response := CreditCard{}
if err = c.SendWithAuth(req, response); err != nil {
if err = c.SendWithAuth(req, &response); err != nil {
return nil, err
}
return response, nil
return &response, nil
}
// DeleteCreditCard func
// Endpoint: DELETE /v1/vault/credit-cards/credit_card_id
func (c *Client) DeleteCreditCard(ctx context.Context, id string) error {
req, err := c.NewRequest(ctx, "DELETE", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), nil)
func (c *Client) DeleteCreditCard(id string) error {
req, err := c.NewRequest("DELETE", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), nil)
if err != nil {
return err
}
@ -39,24 +38,24 @@ func (c *Client) DeleteCreditCard(ctx context.Context, id string) error {
// GetCreditCard func
// Endpoint: GET /v1/vault/credit-cards/credit_card_id
func (c *Client) GetCreditCard(ctx context.Context, id string) (*CreditCard, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), nil)
func (c *Client) GetCreditCard(id string) (*CreditCard, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), nil)
if err != nil {
return nil, err
}
response := &CreditCard{}
response := CreditCard{}
if err = c.SendWithAuth(req, response); err != nil {
if err = c.SendWithAuth(req, &response); err != nil {
return nil, err
}
return response, nil
return &response, nil
}
// GetCreditCards func
// Endpoint: GET /v1/vault/credit-cards
func (c *Client) GetCreditCards(ctx context.Context, ccf *CreditCardsFilter) (*CreditCards, error) {
func (c *Client) GetCreditCards(ccf *CreditCardsFilter) (*CreditCards, error) {
page := 1
if ccf != nil && ccf.Page > 0 {
page = ccf.Page
@ -66,33 +65,33 @@ func (c *Client) GetCreditCards(ctx context.Context, ccf *CreditCardsFilter) (*C
pageSize = ccf.PageSize
}
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s/v1/vault/credit-cards?page=%d&page_size=%d", c.APIBase, page, pageSize), nil)
req, err := c.NewRequest("GET", fmt.Sprintf("%s/v1/vault/credit-cards?page=%d&page_size=%d", c.APIBase, page, pageSize), nil)
if err != nil {
return nil, err
}
response := &CreditCards{}
response := CreditCards{}
if err = c.SendWithAuth(req, response); err != nil {
if err = c.SendWithAuth(req, &response); err != nil {
return nil, err
}
return response, nil
return &response, nil
}
// PatchCreditCard func
// Endpoint: PATCH /v1/vault/credit-cards/credit_card_id
func (c *Client) PatchCreditCard(ctx context.Context, id string, ccf []CreditCardField) (*CreditCard, error) {
req, err := c.NewRequest(ctx, "PATCH", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), ccf)
func (c *Client) PatchCreditCard(id string, ccf []CreditCardField) (*CreditCard, error) {
req, err := c.NewRequest("PATCH", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), ccf)
if err != nil {
return nil, err
}
response := &CreditCard{}
response := CreditCard{}
if err = c.SendWithAuth(req, response); err != nil {
if err = c.SendWithAuth(req, &response); err != nil {
return nil, err
}
return response, nil
return &response, nil
}

View File

@ -1,144 +0,0 @@
package paypal
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
// CreateWebhook - Subscribes your webhook listener to events.
// Endpoint: POST /v1/notifications/webhooks
func (c *Client) CreateWebhook(ctx context.Context, createWebhookRequest *CreateWebhookRequest) (*Webhook, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), createWebhookRequest)
webhook := &Webhook{}
if err != nil {
return webhook, err
}
err = c.SendWithAuth(req, webhook)
return webhook, err
}
// GetWebhook - Shows details for a webhook, by ID.
// Endpoint: GET /v1/notifications/webhooks/ID
func (c *Client) GetWebhook(ctx context.Context, webhookID string) (*Webhook, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/notifications/webhooks/", webhookID), nil)
webhook := &Webhook{}
if err != nil {
return webhook, err
}
err = c.SendWithAuth(req, webhook)
return webhook, err
}
// UpdateWebhook - Updates a webhook to replace webhook fields with new values.
// Endpoint: PATCH /v1/notifications/webhooks/ID
func (c *Client) UpdateWebhook(ctx context.Context, webhookID string, fields []WebhookField) (*Webhook, error) {
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), fields)
webhook := &Webhook{}
if err != nil {
return webhook, err
}
err = c.SendWithAuth(req, webhook)
return webhook, err
}
// ListWebhooks - Lists webhooks for an app.
// Endpoint: GET /v1/notifications/webhooks
func (c *Client) ListWebhooks(ctx context.Context, anchorType string) (*ListWebhookResponse, error) {
if len(anchorType) == 0 {
anchorType = AncorTypeApplication
}
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), nil)
q := req.URL.Query()
q.Add("anchor_type", anchorType)
req.URL.RawQuery = q.Encode()
resp := &ListWebhookResponse{}
if err != nil {
return nil, err
}
err = c.SendWithAuth(req, resp)
return resp, err
}
// DeleteWebhook - Deletes a webhook, by ID.
// Endpoint: DELETE /v1/notifications/webhooks/ID
func (c *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
req, err := c.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), nil)
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// VerifyWebhookSignature - Use this to verify the signature of a webhook recieved from paypal.
// Endpoint: POST /v1/notifications/verify-webhook-signature
func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Request, webhookID string) (*VerifyWebhookResponse, error) {
type verifyWebhookSignatureRequest struct {
AuthAlgo string `json:"auth_algo,omitempty"`
CertURL string `json:"cert_url,omitempty"`
TransmissionID string `json:"transmission_id,omitempty"`
TransmissionSig string `json:"transmission_sig,omitempty"`
TransmissionTime string `json:"transmission_time,omitempty"`
WebhookID string `json:"webhook_id,omitempty"`
Event json.RawMessage `json:"webhook_event,omitempty"`
}
// Read the content
var bodyBytes []byte
if httpReq.Body != nil {
bodyBytes, _ = io.ReadAll(httpReq.Body)
} else {
return nil, errors.New("cannot verify webhook for HTTP Request with empty body")
}
// Restore the io.ReadCloser to its original state
httpReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
verifyRequest := verifyWebhookSignatureRequest{
AuthAlgo: httpReq.Header.Get("PAYPAL-AUTH-ALGO"),
CertURL: httpReq.Header.Get("PAYPAL-CERT-URL"),
TransmissionID: httpReq.Header.Get("PAYPAL-TRANSMISSION-ID"),
TransmissionSig: httpReq.Header.Get("PAYPAL-TRANSMISSION-SIG"),
TransmissionTime: httpReq.Header.Get("PAYPAL-TRANSMISSION-TIME"),
WebhookID: webhookID,
Event: json.RawMessage(bodyBytes),
}
response := &VerifyWebhookResponse{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/verify-webhook-signature"), verifyRequest)
if err != nil {
return nil, err
}
if err = c.SendWithAuth(req, response); err != nil {
return nil, err
}
return response, nil
}
// GetWebhookEventTypes - Lists all webhook event types.
// Endpoint: GET /v1/notifications/webhooks-event-types
func (c *Client) GetWebhookEventTypes(ctx context.Context) (*WebhookEventTypesResponse, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks-event-types"), nil)
q := req.URL.Query()
req.URL.RawQuery = q.Encode()
resp := &WebhookEventTypesResponse{}
if err != nil {
return nil, err
}
err = c.SendWithAuth(req, resp)
return resp, err
}

View File

@ -1,7 +1,6 @@
package paypal
package paypalsdk
import (
"context"
"fmt"
"net/http"
)
@ -11,15 +10,15 @@ import (
// Allows for the customisation of the payment experience
//
// Endpoint: POST /v1/payment-experience/web-profiles
func (c *Client) CreateWebProfile(ctx context.Context, wp WebProfile) (*WebProfile, error) {
func (c *Client) CreateWebProfile(wp WebProfile) (*WebProfile, error) {
url := fmt.Sprintf("%s%s", c.APIBase, "/v1/payment-experience/web-profiles")
req, err := c.NewRequest(ctx, "POST", url, wp)
response := &WebProfile{}
req, err := c.NewRequest("POST", url, wp)
if err != nil {
return response, err
return &WebProfile{}, err
}
response := &WebProfile{}
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
@ -30,11 +29,11 @@ func (c *Client) CreateWebProfile(ctx context.Context, wp WebProfile) (*WebProfi
// GetWebProfile gets an exists payment experience from Paypal
//
// Endpoint: GET /v1/payment-experience/web-profiles/<profile-id>
func (c *Client) GetWebProfile(ctx context.Context, profileID string) (*WebProfile, error) {
func (c *Client) GetWebProfile(profileID string) (*WebProfile, error) {
var wp WebProfile
url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", profileID)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return &wp, err
@ -45,20 +44,20 @@ func (c *Client) GetWebProfile(ctx context.Context, profileID string) (*WebProfi
}
if wp.ID == "" {
return &wp, fmt.Errorf("paypal: unable to get web profile with ID = %s", profileID)
return &wp, fmt.Errorf("paypalsdk: unable to get web profile with ID = %s", profileID)
}
return &wp, nil
}
// GetWebProfiles retrieves web experience profiles from Paypal
// GetWebProfiles retreieves web experience profiles from Paypal
//
// Endpoint: GET /v1/payment-experience/web-profiles
func (c *Client) GetWebProfiles(ctx context.Context) ([]WebProfile, error) {
func (c *Client) GetWebProfiles() ([]WebProfile, error) {
var wps []WebProfile
url := fmt.Sprintf("%s%s", c.APIBase, "/v1/payment-experience/web-profiles")
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return wps, err
@ -74,15 +73,15 @@ func (c *Client) GetWebProfiles(ctx context.Context) ([]WebProfile, error) {
// SetWebProfile sets a web experience profile in Paypal with given id
//
// Endpoint: PUT /v1/payment-experience/web-profiles
func (c *Client) SetWebProfile(ctx context.Context, wp WebProfile) error {
func (c *Client) SetWebProfile(wp WebProfile) error {
if wp.ID == "" {
return fmt.Errorf("paypal: no ID specified for WebProfile")
return fmt.Errorf("paypalsdk: no ID specified for WebProfile")
}
url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", wp.ID)
req, err := c.NewRequest(ctx, "PUT", url, wp)
req, err := c.NewRequest("PUT", url, wp)
if err != nil {
return err
@ -98,11 +97,11 @@ func (c *Client) SetWebProfile(ctx context.Context, wp WebProfile) error {
// DeleteWebProfile deletes a web experience profile from Paypal with given id
//
// Endpoint: DELETE /v1/payment-experience/web-profiles
func (c *Client) DeleteWebProfile(ctx context.Context, profileID string) error {
func (c *Client) DeleteWebProfile(profileID string) error {
url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", profileID)
req, err := c.NewRequest(ctx, "DELETE", url, nil)
req, err := c.NewRequest("DELETE", url, nil)
if err != nil {
return err