Compare commits

..

1 Commits

Author SHA1 Message Date
Roopak Venkatakrishnan
ab4c40e224 Revert "Some updates to refund endpoint (#121)"
This reverts commit efe72c1ed4.
2020-05-03 21:11:51 -07:00
31 changed files with 1211 additions and 2215 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 +1,2 @@
.idea
.vscode
vendor/
.vscode

8
.travis.yml Normal file
View File

@ -0,0 +1,8 @@
language: go
go:
- 1.11
- 1.12
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) 2019 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

144
README.md
View File

@ -1,31 +1,74 @@
[Docs](https://pkg.go.dev/github.com/plutov/paypal)
[![Go Report Card](https://goreportcard.com/badge/plutov/paypal)](https://goreportcard.com/report/plutov/paypal)
[![Build Status](https://travis-ci.org/plutov/paypal.svg?branch=master)](https://travis-ci.org/plutov/paypal)
[![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/plutov/paypal)
<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
Currently supports **v2** only, if you want to use **v1**, use **v1.1.4** git tag.
## Paypal REST API Docs
### Coverage
[Get started with PayPal REST APIs](https://developer.paypal.com/api/rest/)
* POST /v1/oauth2/token
* 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
* GET /v2/payments/authorizations/**ID**
* POST /v2/payments/authorizations/**ID**/capture
* POST /v2/payments/authorizations/**ID**/void
* POST /v2/payments/authorizations/**ID**/reauthorize
* GET /v1/payments/sale/**ID**
* POST /v1/payments/sale/**ID**/refund
* GET /v2/payments/refund/**ID**
* POST /v2/checkout/orders
* GET /v2/checkout/orders/**ID**
* PATCH /v2/checkout/orders/**ID**
* POST /v2/checkout/orders/**ID**/authorize
* POST /v2/checkout/orders/**ID**/capture
* GET /v1/payments/billing-plans
* POST /v1/payments/billing-plans
* PATCH /v1/payments/billing-plans/***ID***
* POST /v1/payments/billing-agreements
* POST /v1/payments/billing-agreements/***TOKEN***/agreement-execute
* POST /v1/notifications/webhooks
* GET /v1/notifications/webhooks
* GET /v1/notifications/webhooks/**ID**
* PATCH /v1/notifications/webhooks/**ID**
* DELETE /v1/notifications/webhooks/**ID**
* POST /v1/notifications/verify-webhook-signature
* POST /v1/reporting/transactions
## Missing endpoints
### Missing endpoints
It is possible that some endpoints are missing in this SDK Client, but you can use built-in **paypal** functions to perform a request: **NewClient -> NewRequest -> SendWithAuth**
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/plutov/paypal"
// If using Go Modules
// import "github.com/plutov/paypal/v3"
// Create a client instance
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox)
c.SetLog(os.Stdout) // Set log to terminal stdout
accessToken, err := c.GetAccessToken()
```
## Get authorization by ID
### Get authorization by ID
```go
auth, err := c.GetAuthorization("2DC87612EK520411B")
@ -49,6 +92,21 @@ auth, err := c.VoidAuthorization(authID)
auth, err := c.ReauthorizeAuthorization(authID, &paypal.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, &paypal.Amount{Total: "7.00", Currency: "USD"})
```
### Get Refund by ID
```go
@ -64,11 +122,7 @@ 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)
order, err := c.CreateOrder(paypal.OrderIntentCapture, []paypal.PurchaseUnitRequest{paypal.PurchaseUnitRequest{ReferenceID: "ref-id", Amount: paypal.Amount{Total: "7.00", Currency: "USD"}}})
```
### Update Order by ID
@ -124,7 +178,7 @@ payout := paypal.Payout{
},
}
payoutResp, err := c.CreatePayout(payout)
payoutResp, err := c.CreateSinglePayout(payout)
```
### Get payout by ID
@ -186,6 +240,7 @@ webprofiles, err := c.GetWebProfiles()
### Update web experience profile
```go
webprofile := WebProfile{
ID: "XP-CP6S-W9DY-96H8-MVN2",
Name: "Shop YeowZa! YeowZa! ",
@ -233,7 +288,6 @@ c.GetCreditCards(nil)
```
### Webhooks
```go
// Create a webhook
c.CreateWebhook(paypal.CreateWebhookRequest{
@ -241,7 +295,7 @@ c.CreateWebhook(paypal.CreateWebhookRequest{
EventTypes: []paypal.WebhookEventType{
paypal.WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
},
})
@ -266,46 +320,22 @@ c.DeleteWebhook("WebhookID")
// List registered webhooks
c.ListWebhooks(paypal.AncorTypeApplication)
```
### Generate Next Invoice Number
### How to Contribute
```go
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
c.GenerateInvoiceNumber(ctx) // might return something like "0001" or "0010".
```
* Fork a repository
* Add/Fix something
* Check that tests are passing
* Create PR
### Get Invoice Details by ID
Current contributors:
```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)
- [Alex Pliutau](https://github.com/plutov)
## Tests
### Tests
```
go test -v ./...
```
* Unit tests: `go test -v ./...`
* Integration tests: `go test -tags=integration`

View File

@ -2,16 +2,15 @@ package paypal
import (
"bytes"
"context"
"fmt"
"net/http"
)
// GetAuthorization returns an authorization by ID
// Endpoint: GET /v2/payments/authorizations/ID
func (c *Client) GetAuthorization(ctx context.Context, authID string) (*Authorization, error) {
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)
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/payments/authorizations/", authID), buf)
auth := &Authorization{}
if err != nil {
@ -25,19 +24,19 @@ func (c *Client) GetAuthorization(ctx context.Context, authID string) (*Authoriz
// 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, "")
func (c *Client) CaptureAuthorization(authID string, paymentCaptureRequest *PaymentCaptureRequest) (*PaymentCaptureResponse, error) {
return c.CaptureAuthorizationWithPaypalRequestId(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,
func (c *Client) CaptureAuthorizationWithPaypalRequestId(
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)
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/capture"), paymentCaptureRequest)
paymentCaptureResponse := &PaymentCaptureResponse{}
if err != nil {
@ -54,9 +53,9 @@ func (c *Client) CaptureAuthorizationWithPaypalRequestId(ctx context.Context,
// VoidAuthorization voids a previously authorized payment
// Endpoint: POST /v2/payments/authorizations/ID/void
func (c *Client) VoidAuthorization(ctx context.Context, authID string) (*Authorization, error) {
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)
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/void"), buf)
auth := &Authorization{}
if err != nil {
@ -68,11 +67,11 @@ func (c *Client) VoidAuthorization(ctx context.Context, authID string) (*Authori
}
// ReauthorizeAuthorization reauthorize a Paypal account payment.
// PayPal recommends reauthorizing payment after ~3 days
// PayPal recommends to reauthorize 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)
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, "/v2/payments/authorizations/"+authID+"/reauthorize"), buf)
auth := &Authorization{}
if err != nil {

133
billing.go Normal file
View File

@ -0,0 +1,133 @@
package paypal
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"`
}
// BillingPlanListParams struct
BillingPlanListParams struct {
Page string `json:"page,omitempty"` //Default: 0.
Status string `json:"status,omitempty"` //Allowed values: CREATED, ACTIVE, INACTIVE, ALL.
PageSize string `json:"page_size,omitempty"` //Default: 10.
TotalRequired string `json:"total_required,omitempty"` //Default: no.
}
//BillingPlanListResp struct
BillingPlanListResp struct {
Plans []BillingPlan `json:"plans,omitempty"`
TotalItems string `json:"total_items,omitempty"`
TotalPages string `json:"total_pages,omitempty"`
Links []Link `json:"links,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)
response := &ExecuteAgreementResponse{}
if err != nil {
return response, err
}
req.SetBasicAuth(c.ClientID, c.Secret)
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
if response.ID == "" {
return response, errors.New("Unable to execute agreement with token=" + token)
}
return response, err
}
// ListBillingPlans lists billing-plans
// Endpoint: GET /v1/payments/billing-plans
func (c *Client) ListBillingPlans(bplp BillingPlanListParams) (*BillingPlanListResp, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), nil)
q := req.URL.Query()
q.Add("page", bplp.Page)
q.Add("page_size", bplp.PageSize)
q.Add("status", bplp.Status)
q.Add("total_required", bplp.TotalRequired)
req.URL.RawQuery = q.Encode()
response := &BillingPlanListResp{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}

View File

@ -2,11 +2,11 @@ package paypal
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"time"
@ -30,9 +30,9 @@ 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
}
@ -77,7 +77,7 @@ func (c *Client) SetReturnRepresentation() {
}
// 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 (
@ -97,36 +97,21 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
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
@ -136,8 +121,8 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
}
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 +133,25 @@ 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()
c.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 {
c.Unlock()
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()
c.Unlock()
return c.Send(req, v)
}
@ -179,7 +164,7 @@ 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 {
b, err := json.Marshal(&payload)
@ -188,5 +173,24 @@ func (c *Client) NewRequest(ctx context.Context, method, url string, payload int
}
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"
)

17
example_test.go Normal file
View File

@ -0,0 +1,17 @@
package paypal_test
import "github.com/plutov/paypal/v3"
func Example() {
// Initialize client
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox)
if err != nil {
panic(err)
}
// Retrieve access token
_, err = c.GetAccessToken()
if err != nil {
panic(err)
}
}

12
go.mod
View File

@ -1,11 +1,3 @@
module euphoria-laxis.fr/go-packages/paypale/v4
module github.com/plutov/paypal/v3
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
)
go 1.12

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,17 +1,65 @@
package paypal
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, 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) {
// 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
}

214
integration_test.go Normal file
View File

@ -0,0 +1,214 @@
// +build integration
package paypal
import (
"testing"
)
// All test values are defined here
var testClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
var testSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
var testUserID = "https://www.paypal.com/webapps/auth/identity/user/VBqgHcgZwb1PBs69ybjjXfIW86_Hr93aBvF_Rgbh2II"
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 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, got %s, error: %v", testUserID, u.ID, err)
}
}
func TestCreateSinglePayout(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
payout := Payout{
SenderBatchHeader: &SenderBatchHeader{
SenderBatchID: "Payouts_2018_100007",
EmailSubject: "You have a payout!",
EmailMessage: "You have received a payout! Thanks for using our service!",
},
Items: []PayoutItem{
{
RecipientType: "EMAIL",
Receiver: "receiver@example.com",
Amount: &AmountPayout{
Value: "9.87",
Currency: "USD",
},
Note: "Thanks for your patronage!",
SenderItemID: "201403140001",
},
},
}
c.CreateSinglePayout(payout)
}
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")
}
}
// Creates, gets, and deletes single webhook
func TestCreateAndGetWebhook(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
payload := &CreateWebhookRequest{
URL: "https://example.com/paypal_webhooks",
EventTypes: []WebhookEventType{
WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
}
createdWebhook, err := c.CreateWebhook(payload)
if err != nil {
t.Errorf("Webhook couldn't be created, error %v", err)
}
_, err = c.GetWebhook(createdWebhook.ID)
if err != nil {
t.Errorf("An error occurred while getting webhook, error %v", err)
}
err = c.DeleteWebhook(createdWebhook.ID)
if err != nil {
t.Errorf("An error occurred while webhooks deletion, error %v", err)
}
}
// Creates, updates, and deletes single webhook
func TestCreateAndUpdateWebhook(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
creationPayload := &CreateWebhookRequest{
URL: "https://example.com/paypal_webhooks",
EventTypes: []WebhookEventType{
WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
}
createdWebhook, err := c.CreateWebhook(creationPayload)
if err != nil {
t.Errorf("Webhook couldn't be created, error %v", err)
}
updatePayload := []WebhookField{
WebhookField{
Operation: "replace",
Path: "/event_types",
Value: []interface{}{
map[string]interface{}{
"name": "PAYMENT.SALE.REFUNDED",
},
},
},
}
_, err = c.UpdateWebhook(createdWebhook.ID, updatePayload)
if err != nil {
t.Errorf("Couldn't update webhook, error %v", err)
}
err = c.DeleteWebhook(createdWebhook.ID)
if err != nil {
t.Errorf("An error occurred while webhooks deletion, error %v", err)
}
}
func TestListWebhooks(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()
_, err := c.ListWebhooks(AncorTypeApplication)
if err != nil {
t.Errorf("Cannot registered list webhooks, error %v", err)
}
}

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)
}

108
order.go
View File

@ -1,17 +1,13 @@
package paypal
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) {
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, "/v2/checkout/orders/", orderID), nil)
if err != nil {
return order, err
}
@ -23,39 +19,23 @@ func (c *Client) GetOrder(ctx context.Context, orderID string) (*Order, error) {
return order, nil
}
// Create an order
// CreateOrder - Use this call to 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) {
func (c *Client) CreateOrder(intent string, purchaseUnits []PurchaseUnitRequest, payer *CreateOrderPayer, appContext *ApplicationContext) (*Order, error) {
type createOrderRequest struct {
Intent string `json:"intent"`
PaymentSource *PaymentSource `json:"payment_source,omitempty"`
Payer *CreateOrderPayer `json:"payer,omitempty"`
PurchaseUnits []PurchaseUnitRequest `json:"purchase_units"`
ApplicationContext *ApplicationContext `json:"application_context,omitempty"`
}
order := &Order{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders"), createOrderRequest{Intent: intent, PurchaseUnits: purchaseUnits, PaymentSource: paymentSource, ApplicationContext: appContext})
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders"), createOrderRequest{Intent: intent, PurchaseUnits: purchaseUnits, Payer: payer, 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
}
@ -65,37 +45,27 @@ func (c *Client) CreateOrderWithPaypalRequestID(ctx context.Context,
// 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 {
func (c *Client) UpdateOrder(orderID string, purchaseUnits []PurchaseUnitRequest) (*Order, error) {
order := &Order{}
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,
},
})
req, err := c.NewRequest("PATCH", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), purchaseUnits)
if err != nil {
return err
return order, err
}
if err = c.SendWithAuth(req, nil); err != nil {
return err
if err = c.SendWithAuth(req, order); err != nil {
return order, err
}
return nil
return order, 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{}
func (c *Client) AuthorizeOrder(orderID string, authorizeOrderRequest AuthorizeOrderRequest) (*Authorization, error) {
auth := &Authorization{}
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, "/v2/checkout/orders/"+orderID+"/authorize"), authorizeOrderRequest)
if err != nil {
return auth, err
}
@ -109,23 +79,22 @@ func (c *Client) AuthorizeOrder(ctx context.Context, orderID string, authorizeOr
// 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)
func (c *Client) CaptureOrder(orderID string, captureOrderRequest CaptureOrderRequest) (*CaptureOrderResponse, error) {
return c.CaptureOrderWithPaypalRequestId(orderID, captureOrderRequest, "")
}
// 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,
func (c *Client) CaptureOrderWithPaypalRequestId(
orderID string,
captureOrderRequest CaptureOrderRequest,
requestID string,
mockResponse *CaptureOrderMockResponse,
) (*CaptureOrderResponse, error) {
capture := &CaptureOrderResponse{}
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, "/v2/checkout/orders/"+orderID+"/capture"), captureOrderRequest)
if err != nil {
return capture, err
}
@ -134,15 +103,6 @@ func (c *Client) CaptureOrderWithPaypalRequestId(ctx context.Context,
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
}
@ -152,20 +112,20 @@ func (c *Client) CaptureOrderWithPaypalRequestId(ctx context.Context,
// 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, "")
func (c *Client) RefundCapture(captureID string, refundCaptureRequest RefundCaptureRequest) (*RefundResponse, error) {
return c.RefundCaptureWithPaypalRequestId(captureID, refundCaptureRequest, "")
}
// 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,
func (c *Client) RefundCaptureWithPaypalRequestId(
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, "/v2/payments/captures/"+captureID+"/refund"), refundCaptureRequest)
if err != nil {
return refund, err
}
@ -179,19 +139,3 @@ func (c *Client) RefundCaptureWithPaypalRequestId(ctx context.Context,
}
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
}

View File

@ -1,15 +1,14 @@
package paypal
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)
func (c *Client) CreateSinglePayout(p Payout) (*PayoutResponse, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts"), p)
response := &PayoutResponse{}
if err != nil {
@ -26,8 +25,8 @@ 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)
func (c *Client) GetPayout(payoutBatchID string) (*PayoutResponse, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts/"+payoutBatchID), nil)
response := &PayoutResponse{}
if err != nil {
@ -44,8 +43,8 @@ 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)
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)
response := &PayoutItemResponse{}
if err != nil {
@ -62,8 +61,8 @@ 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)
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)
response := &PayoutItemResponse{}
if err != nil {

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
}

56
sale.go
View File

@ -1,17 +1,55 @@
package paypal
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 /v2/payments/refund/ID
func (c *Client) GetRefund(refundID string) (*Refund, error) {
refund := &Refund{}
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/refund/"+refundID), nil)
if err != nil {
return refund, err
}

View File

@ -1,149 +1,33 @@
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"`
ID string `json:"id,omitempty"`
PlanID string `json:"plan_id,omitempty"`
StartTime time.Time `json:"start_time,omitempty"`
Quantity string `json:"quantity,omitempty"`
ShippingAmount ShippingAmount `json:"shipping_amount,omitempty"`
Subscriber Subscriber `json:"subscriber,omitempty"`
BillingInfo BillingInfo `json:"billing_info,omitempty"`
CreateTime time.Time `json:"create_time,omitempty"`
UpdateTime time.Time `json:"update_time,omitempty"`
Links []Link `json:"links,omitempty"`
Status string `json:"status,omitempty"`
StatusUpdateTime time.Time `json:"status_update_time,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)
func (c *Client) GetSubscriptionDetails(subscriptionID string) (*SubscriptionDetailResp, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/billing/subscriptions/%s", c.APIBase, subscriptionID), nil)
response := &SubscriptionDetailResp{}
if err != nil {
return response, err
@ -151,84 +35,3 @@ func (c *Client) GetSubscriptionDetails(ctx context.Context, subscriptionID stri
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,7 +1,6 @@
package paypal
import (
"context"
"fmt"
"strconv"
"time"
@ -31,15 +30,17 @@ type TransactionSearchResponse struct {
EndDate JSONTime `json:"end_date"`
LastRefreshDatetime JSONTime `json:"last_refreshed_datetime"`
Page int `json:"page"`
SharedListResponse
TotalItems int `json:"total_items"`
TotalPages int `json:"total_pages"`
Links []Link `json:"links"`
}
// 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) {
func (c *Client) ListTransactions(req *TransactionSearchRequest) (*TransactionSearchResponse, error) {
response := &TransactionSearchResponse{}
r, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/reporting/transactions"), nil)
r, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/reporting/transactions"), nil)
if err != nil {
return nil, err
}

621
types.go
View File

@ -12,10 +12,10 @@ import (
const (
// APIBaseSandBox points to the sandbox (for testing) version of the API
APIBaseSandBox = "https://api-m.sandbox.paypal.com"
APIBaseSandBox = "https://api.sandbox.paypal.com"
// APIBaseLive points to the live version of the API
APIBaseLive = "https://api-m.paypal.com"
APIBaseLive = "https://api.paypal.com"
// RequestNewTokenBeforeExpiresIn is used by SendWithAuth and try to get new Token when it's about to expire
RequestNewTokenBeforeExpiresIn = time.Duration(60) * time.Second
@ -85,9 +85,13 @@ const (
// Possible values for `shipping_preference` in ApplicationContext
//
// https://developer.paypal.com/docs/api/orders/v2/#definition-application_context
const (
ShippingPreferenceGetFromFile string = "GET_FROM_FILE"
ShippingPreferenceNoShipping string = "NO_SHIPPING"
ShippingPreferenceSetProvidedAddress string = "SET_PROVIDED_ADDRESS"
)
const (
EventCheckoutOrderApproved string = "CHECKOUT.ORDER.APPROVED"
EventPaymentCaptureCompleted string = "PAYMENT.CAPTURE.COMPLETED"
EventPaymentCaptureDenied string = "PAYMENT.CAPTURE.DENIED"
EventPaymentCaptureRefunded string = "PAYMENT.CAPTURE.REFUNDED"
@ -116,31 +120,6 @@ const (
FeatureUpdateCustomerDispute string = "UPDATE_CUSTOMER_DISPUTES"
)
// https://developer.paypal.com/docs/api/payments.payouts-batch/v1/?mark=recipient_type#definition-recipient_type
const (
EmailRecipientType string = "EMAIL" // An unencrypted email — string of up to 127 single-byte characters.
PaypalIdRecipientType string = "PAYPAL_ID" // An encrypted PayPal account number.
PhoneRecipientType string = "PHONE" // An unencrypted phone number.
// Note: The PayPal sandbox doesn't support type PHONE
)
// https://developer.paypal.com/docs/api/payments.payouts-batch/v1/?mark=recipient_wallet#definition-recipient_wallet
const (
PaypalRecipientWallet string = "PAYPAL"
VenmoRecipientWallet string = "VENMO"
)
// Possible value for `batch_status` in GetPayout
//
// https://developer.paypal.com/docs/api/payments.payouts-batch/v1/#definition-batch_status
const (
BatchStatusDenied string = "DENIED"
BatchStatusPending string = "PENDING"
BatchStatusProcessing string = "PROCESSING"
BatchStatusSuccess string = "SUCCESS"
BatchStatusCanceled string = "CANCELED"
)
const (
LinkRelSelf string = "self"
LinkRelActionURL string = "action_url"
@ -157,10 +136,10 @@ type (
// Address struct
Address struct {
Line1 string `json:"line1,omitempty"`
Line1 string `json:"line1"`
Line2 string `json:"line2,omitempty"`
City string `json:"city,omitempty"`
CountryCode string `json:"country_code,omitempty"`
City string `json:"city"`
CountryCode string `json:"country_code"`
PostalCode string `json:"postal_code,omitempty"`
State string `json:"state,omitempty"`
Phone string `json:"phone,omitempty"`
@ -192,280 +171,14 @@ type (
}
// ApplicationContext struct
//Doc: https://developer.paypal.com/docs/api/orders/v2/#definition-application_context
ApplicationContext struct {
BrandName string `json:"brand_name,omitempty"`
Locale string `json:"locale,omitempty"`
ShippingPreference ShippingPreference `json:"shipping_preference,omitempty"`
UserAction UserAction `json:"user_action,omitempty"`
PaymentMethod PaymentMethod `json:"payment_method,omitempty"`
LandingPage string `json:"landing_page,omitempty"`
ReturnURL string `json:"return_url,omitempty"`
CancelURL string `json:"cancel_url,omitempty"`
}
// Invoicing relates structures
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_generate-next-invoice-number
InvoiceNumber struct {
InvoiceNumberValue string `json:"invoice_number"`
}
// used in InvoiceAmountWithBreakdown
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-custom_amount
CustomAmount struct {
Label string `json:"label"`
Amount Money `json:"amount,omitempty"`
}
// Used in AggregatedDiscount
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-discount
InvoicingDiscount struct {
DiscountAmount Money `json:"amount,omitempty"`
Percent string `json:"percent,omitempty"`
}
// Used in InvoiceAmountWithBreakdown
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-aggregated_discount
AggregatedDiscount struct {
InvoiceDiscount InvoicingDiscount `json:"invoice_discount,omitempty"`
ItemDiscount *Money `json:"item_discount,omitempty"`
}
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-tax
InvoiceTax struct {
Name string `json:"name,omitempty"`
Percent string `json:"percent,omitempty"`
ID string `json:"id,omitempty"` // not mentioned here, but is still returned in response payload, when invoice is requested by ID.
Amount Money `json:"amount,omitempty"`
}
// Used in InvoiceAmountWithBreakdown struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-shipping_cost
InvoiceShippingCost struct {
Amount Money `json:"amount,omitempty"`
Tax InvoiceTax `json:"tax,omitempty"`
}
// Used in AmountSummaryDetail
// Doc: https://developer.paypal.com/docs/api/payments/v2/#definition-nrp-nrr_attributes
InvoiceAmountWithBreakdown struct {
Custom CustomAmount `json:"custom,omitempty"` // The custom amount to apply to an invoice.
Discount AggregatedDiscount `json:"discount,omitempty"`
ItemTotal Money `json:"item_total,omitempty"` // The subtotal for all items.
Shipping InvoiceShippingCost `json:"shipping,omitempty"` // The shipping fee for all items. Includes tax on shipping.
TaxTotal Money `json:"tax_total,omitempty"`
}
// Invoice AmountSummary
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-amount_summary_detail
AmountSummaryDetail struct {
Breakdown InvoiceAmountWithBreakdown `json:"breakdown,omitempty"`
Currency string `json:"currency_code,omitempty"`
Value string `json:"value,omitempty"`
}
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-partial_payment
InvoicePartialPayment struct {
AllowPartialPayment bool `json:"allow_partial_payment,omitempty"`
MinimumAmountDue Money `json:"minimum_amount_due,omitempty"` // Valid only when allow_partial_payment is true.
}
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-configuration
InvoiceConfiguration struct {
AllowTip bool `json:"allow_tip,omitempty"`
PartialPayment InvoicePartialPayment `json:"partial_payment,omitempty"`
TaxCalculatedAfterDiscount bool `json:"tax_calculated_after_discount,omitempty"`
TaxInclusive bool `json:"tax_inclusive,omitempty"`
TemplateId string `json:"template_id,omitempty"`
}
// used in InvoiceDetail structure
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-file_reference
InvoiceFileReference struct {
ContentType string `json:"content_type,omitempty"`
CreateTime string `json:"create_time,omitempty"`
ID string `json:"id,omitempty"`
URL string `json:"reference_url,omitempty"`
Size string `json:"size,omitempty"`
}
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-metadata
InvoiceAuditMetadata struct {
CreateTime string `json:"create_time,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
LastUpdateTime string `json:"last_update_time,omitempty"`
LastUpdatedBy string `json:"last_updated_by,omitempty"`
CancelTime string `json:"cancel_time,omitempty"`
CancellledTimeBy string `json:"cancelled_by,omitempty"`
CreatedByFlow string `json:"created_by_flow,omitempty"`
FirstSentTime string `json:"first_sent_time,omitempty"`
InvoicerViewUrl string `json:"invoicer_view_url,omitempty"`
LastSentBy string `json:"last_sent_by,omitempty"`
LastSentTime string `json:"last_sent_time,omitempty"`
RecipientViewUrl string `json:"recipient_view_url,omitempty"`
}
// used in InvoiceDetail struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-invoice_payment_term
InvoicePaymentTerm struct {
TermType string `json:"term_type,omitempty"`
DueDate string `json:"due_date,omitempty"`
}
// used in Invoice struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-invoice_detail
InvoiceDetail struct {
CurrencyCode string `json:"currency_code"` // required, hence omitempty not used
Attachments []InvoiceFileReference `json:"attachments,omitempty"`
Memo string `json:"memo,omitempty"`
Note string `json:"note,omitempty"`
Reference string `json:"reference,omitempty"`
TermsAndConditions string `json:"terms_and_conditions,omitempty"`
InvoiceDate string `json:"invoice_date,omitempty"`
InvoiceNumber string `json:"invoice_number,omitempty"`
Metadata InvoiceAuditMetadata `json:"metadata,omitempty"` // The audit metadata.
PaymentTerm InvoicePaymentTerm `json:"payment_term,omitempty"` // payment due date for the invoice. Value is either but not both term_type or due_date.
}
// used in InvoicerInfo struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-phone_detail
InvoicerPhoneDetail struct {
CountryCode string `json:"country_code"`
NationalNumber string `json:"national_number"`
ExtensionNumber string `json:"extension_number,omitempty"`
PhoneType string `json:"phone_type,omitempty"`
}
// used in Invoice struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-invoicer_info
InvoicerInfo struct {
AdditionalNotes string `json:"additional_notes,omitempty"`
EmailAddress string `json:"email_address,omitempty"`
LogoUrl string `json:"logo_url,omitempty"`
Phones []InvoicerPhoneDetail `json:"phones,omitempty"`
TaxId string `json:"tax_id,omitempty"`
Website string `json:"website,omitempty"`
}
// Used in Invoice struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-item
InvoiceItem struct {
Name string `json:"name"`
Quantity string `json:"quantity"`
UnitAmount Money `json:"unit_amount"`
Description string `json:"description,omitempty"`
InvoiceDiscount InvoicingDiscount `json:"discount,omitempty"`
ID string `json:"id,omitempty"`
ItemDate string `json:"item_date,omitempty"`
Tax InvoiceTax `json:"tax,omitempty"`
UnitOfMeasure string `json:"unit_of_measure,omitempty"`
}
// used in InvoiceAddressPortable
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-address_details
InvoiceAddressDetails struct {
BuildingName string `json:"building_name,omitempty"`
DeliveryService string `json:"delivery_service,omitempty"`
StreetName string `json:"street_name,omitempty"`
StreetNumber string `json:"street_number,omitempty"`
StreetType string `json:"street_type,omitempty"`
SubBuilding string `json:"sub_building,omitempty"`
}
// used in InvoiceContactInfo
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-address_portable
InvoiceAddressPortable struct {
CountryCode string `json:"country_code"`
AddressDetails InvoiceAddressDetails `json:"address_details,omitempty"`
AddressLine1 string `json:"address_line_1,omitempty"`
AddressLine2 string `json:"address_line_2,omitempty"`
AddressLine3 string `json:"address_line_3,omitempty"`
AdminArea1 string `json:"admin_area_1,omitempty"`
AdminArea2 string `json:"admin_area_2,omitempty"`
AdminArea3 string `json:"admin_area_3,omitempty"`
AdminArea4 string `json:"admin_area_4,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
}
// used in InvoicePaymentDetails
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-contact_information
InvoiceContactInfo struct {
BusinessName string `json:"business_name,omitempty"`
RecipientAddress InvoiceAddressPortable `json:"address,omitempty"` // address of the recipient.
RecipientName Name `json:"name,omitempty"` // The first and Last name of the recipient.
}
//used in InvoicePayments struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-payment_detail
InvoicePaymentDetails struct {
Method string `json:"method"`
Amount Money `json:"amount,omitempty"`
Note string `json:"note,omitempty"`
PaymentDate string `json:"payment_date,omitempty"`
PaymentID string `json:"payment_id,omitempty"`
ShippingInfo InvoiceContactInfo `json:"shipping_info,omitempty"` // The recipient's shipping information.
Type string `json:"type,omitempty"`
}
// used in Invoice
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-payments
InvoicePayments struct {
PaidAmount Money `json:"paid_amount,omitempty"`
Transactions []InvoicePaymentDetails `json:"transactions,omitempty"`
}
// used in InvoiceRecipientInfo
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-billing_info
InvoiceBillingInfo struct {
AdditionalInfo string `json:"additional_info,omitempty"`
EmailAddress string `json:"email_address,omitempty"`
Language string `json:"language,omitempty"`
Phones []InvoicerPhoneDetail `json:"phones,omitempty"` // invoice recipient's phone numbers.
}
// used in Invoice struct
// Doc:
InvoiceRecipientInfo struct {
BillingInfo InvoiceBillingInfo `json:"billing_info,omitempty"` // billing information for the invoice recipient.
ShippingInfo InvoiceContactInfo `json:"shipping_info,omitempty"` // recipient's shipping information.
}
// used in InvoiceRefund struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-refund_detail
InvoiceRefundDetails struct {
Method string `json:"method"`
RefundAmount Money `json:"amount,omitempty"`
RefundDate string `json:"refund_date,omitempty"`
RefundID string `json:"refund_id,omitempty"`
RefundType string `json:"type,omitempty"`
}
// used in Invoice struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-refunds
InvoiceRefund struct {
RefundAmount Money `json:"refund_amount,omitempty"`
RefundDetails []InvoiceRefundDetails `json:"transactions,omitempty"`
}
// used in Invoice struct
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-email_address
InvoiceEmailAddress struct {
EmailAddress string `json:"email_address,omitempty"`
}
// to contain Invoice related fields
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_get
Invoice struct {
AdditionalRecipients []InvoiceEmailAddress `json:"additional_recipients,omitempty"` // An array of one or more CC: emails to which notifications are sent.
AmountSummary AmountSummaryDetail `json:"amount,omitempty"`
Configuration InvoiceConfiguration `json:"configuration,omitempty"`
Detail InvoiceDetail `json:"detail,omitempty"`
DueAmount Money `json:"due_amount,omitempty"` // balance amount outstanding after payments.
Gratuity Money `json:"gratuity,omitempty"` // amount paid by the payer as gratuity to the invoicer.
ID string `json:"id,omitempty"`
Invoicer InvoicerInfo `json:"invoicer,omitempty"`
Items []InvoiceItem `json:"items,omitempty"`
Links []Link `json:"links,omitempty"`
ParentID string `json:"parent_id,omitempty"`
Payments InvoicePayments `json:"payments,omitempty"`
PrimaryRecipients []InvoiceRecipientInfo `json:"primary_recipients,omitempty"`
Refunds InvoiceRefund `json:"refunds,omitempty"` // List of refunds against this invoice.
Status string `json:"status,omitempty"`
}
// Doc: https://developer.paypal.com/api/orders/v2/#definition-payment_method
PaymentMethod struct {
PayeePreferred PayeePreferred `json:"payee_preferred,omitempty"`
StandardEntryClassCode StandardEntryClassCode `json:"standard_entry_class_code,omitempty"`
BrandName string `json:"brand_name,omitempty"`
Locale string `json:"locale,omitempty"`
LandingPage string `json:"landing_page,omitempty"`
ShippingPreference string `json:"shipping_preference,omitempty"`
UserAction string `json:"user_action,omitempty"`
ReturnURL string `json:"return_url,omitempty"`
CancelURL string `json:"cancel_url,omitempty"`
}
// Authorization struct
@ -483,13 +196,14 @@ type (
Links []Link `json:"links,omitempty"`
}
// AuthorizeOrderResponse .
AuthorizeOrderResponse struct {
CreateTime *time.Time `json:"create_time,omitempty"`
UpdateTime *time.Time `json:"update_time,omitempty"`
ID string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
Intent string `json:"intent,omitempty"`
PurchaseUnits []PurchaseUnit `json:"purchase_units,omitempty"`
PurchaseUnits []PurchaseUnitRequest `json:"purchase_units,omitempty"`
Payer *PayerWithNameAndPhone `json:"payer,omitempty"`
}
@ -541,33 +255,11 @@ type (
Links []Link `json:"links,omitempty"`
}
//https://developer.paypal.com/docs/api/payments/v2/#captures_get
CaptureDetailsResponse struct {
Status string `json:"status,omitempty"`
StatusDetails *CaptureStatusDetails `json:"status_details,omitempty"`
ID string `json:"id,omitempty"`
Amount *Money `json:"amount,omitempty"`
InvoiceID string `json:"invoice_id,omitempty"`
CustomID string `json:"custom_id,omitempty"`
SellerProtection *SellerProtection `json:"seller_protection,omitempty"`
FinalCapture bool `json:"final_capture,omitempty"`
SellerReceivableBreakdown *SellerReceivableBreakdown `json:"seller_receivable_breakdown,omitempty"`
DisbursementMode string `json:"disbursement_mode,omitempty"`
Links []Link `json:"links,omitempty"`
UpdateTime *time.Time `json:"update_time,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
}
// CaptureOrderRequest - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
CaptureOrderRequest struct {
PaymentSource *PaymentSource `json:"payment_source"`
}
// CaptureOrderMockResponse - https://developer.paypal.com/docs/api-basics/sandbox/request-headers/#test-api-error-handling-routines
CaptureOrderMockResponse struct {
MockApplicationCodes string `json:"mock_application_codes"`
}
// RefundOrderRequest - https://developer.paypal.com/docs/api/payments/v2/#captures_refund
RefundCaptureRequest struct {
Amount *Money `json:"amount,omitempty"`
@ -586,14 +278,15 @@ type (
SenderBatchHeader *SenderBatchHeader `json:"sender_batch_header,omitempty"`
}
// Plan struct
Plan struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreateTime string `json:"create_time,omitempty"`
UpdateTime string `json:"update_time,omitempty"`
PaymentDefinitions []PaymentDefinition `json:"payment_definitions,omitempty"`
// BillingAgreement struct
BillingAgreement struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
StartDate JSONTime `json:"start_date,omitempty"`
Plan BillingPlan `json:"plan,omitempty"`
Payer Payer `json:"payer,omitempty"`
ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"`
OverrideMerchantPreferences *MerchantPreferences `json:"override_merchant_preferences,omitempty"`
}
// BillingInfo struct
@ -617,14 +310,13 @@ type (
// Capture struct
Capture struct {
ID string `json:"id,omitempty"`
Amount *Amount `json:"amount,omitempty"`
State string `json:"state,omitempty"`
ParentPayment string `json:"parent_payment,omitempty"`
TransactionFee string `json:"transaction_fee,omitempty"`
IsFinalCapture bool `json:"is_final_capture"`
CreateTime *time.Time `json:"create_time,omitempty"`
UpdateTime *time.Time `json:"update_time,omitempty"`
State string `json:"state,omitempty"`
ParentPayment string `json:"parent_payment,omitempty"`
ID string `json:"id,omitempty"`
Links []Link `json:"links,omitempty"`
}
@ -636,8 +328,7 @@ type (
// Client represents a Paypal REST API Client
Client struct {
// sync.Mutex
mu sync.Mutex
sync.Mutex
Client *http.Client
ClientID string
Secret string
@ -667,8 +358,10 @@ type (
// CreditCards GET /v1/vault/credit-cards
CreditCards struct {
Items []CreditCard `json:"items"`
SharedListResponse
Items []CreditCard `json:"items"`
Links []Link `json:"links"`
TotalItems int `json:"total_items"`
TotalPages int `json:"total_pages"`
}
// CreditCardToken struct
@ -727,12 +420,9 @@ type (
// ErrorResponseDetail struct
ErrorResponseDetail struct {
Field string `json:"field"`
Issue string `json:"issue"`
Name string `json:"name"`
Message string `json:"message"`
Description string `json:"description"`
Links []Link `json:"link"`
Field string `json:"field"`
Issue string `json:"issue"`
Links []Link `json:"link"`
}
// ErrorResponse https://developer.paypal.com/docs/api/errors/
@ -825,19 +515,10 @@ type (
Value string `json:"value"`
}
// PurchaseUnit struct
PurchaseUnit struct {
ReferenceID string `json:"reference_id"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
Payee *PayeeForOrders `json:"payee,omitempty"`
Payments *CapturedPayments `json:"payments,omitempty"`
PaymentInstruction *PaymentInstruction `json:"payment_instruction,omitempty"`
Description string `json:"description,omitempty"`
CustomID string `json:"custom_id,omitempty"`
InvoiceID string `json:"invoice_id,omitempty"`
ID string `json:"id,omitempty"`
SoftDescriptor string `json:"soft_descriptor,omitempty"`
Shipping *ShippingDetail `json:"shipping,omitempty"`
Items []Item `json:"items,omitempty"`
ReferenceID string `json:"reference_id"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
}
// TaxInfo used for orders.
@ -876,16 +557,15 @@ type (
// PurchaseUnitRequest struct
PurchaseUnitRequest struct {
ReferenceID string `json:"reference_id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount"`
Payee *PayeeForOrders `json:"payee,omitempty"`
Description string `json:"description,omitempty"`
CustomID string `json:"custom_id,omitempty"`
InvoiceID string `json:"invoice_id,omitempty"`
SoftDescriptor string `json:"soft_descriptor,omitempty"`
Items []Item `json:"items,omitempty"`
Shipping *ShippingDetail `json:"shipping,omitempty"`
PaymentInstruction *PaymentInstruction `json:"payment_instruction,omitempty"`
ReferenceID string `json:"reference_id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount"`
Payee *PayeeForOrders `json:"payee,omitempty"`
Description string `json:"description,omitempty"`
CustomID string `json:"custom_id,omitempty"`
InvoiceID string `json:"invoice_id,omitempty"`
SoftDescriptor string `json:"soft_descriptor,omitempty"`
Items []Item `json:"items,omitempty"`
Shipping *ShippingDetail `json:"shipping,omitempty"`
}
// MerchantPreferences struct
@ -900,50 +580,25 @@ type (
// Order struct
Order struct {
ID string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
Intent string `json:"intent,omitempty"`
Payer *PayerWithNameAndPhone `json:"payer,omitempty"`
PurchaseUnits []PurchaseUnit `json:"purchase_units,omitempty"`
Links []Link `json:"links,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
UpdateTime *time.Time `json:"update_time,omitempty"`
}
// ExchangeRate struct
//
// https://developer.paypal.com/docs/api/orders/v2/#definition-exchange_rate
ExchangeRate struct {
SourceCurrency string `json:"source_currency"`
TargetCurrency string `json:"target_currency"`
Value string `json:"value"`
}
// SellerReceivableBreakdown has the detailed breakdown of the capture activity.
SellerReceivableBreakdown struct {
GrossAmount *Money `json:"gross_amount,omitempty"`
PaypalFee *Money `json:"paypal_fee,omitempty"`
PaypalFeeInReceivableCurrency *Money `json:"paypal_fee_in_receivable_currency,omitempty"`
NetAmount *Money `json:"net_amount,omitempty"`
ReceivableAmount *Money `json:"receivable_amount,omitempty"`
ExchangeRate *ExchangeRate `json:"exchange_rate,omitempty"`
PlatformFees []PlatformFee `json:"platform_fees,omitempty"`
ID string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
Intent string `json:"intent,omitempty"`
PurchaseUnits []PurchaseUnit `json:"purchase_units,omitempty"`
Links []Link `json:"links,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
UpdateTime *time.Time `json:"update_time,omitempty"`
}
// CaptureAmount struct
CaptureAmount struct {
Status string `json:"status,omitempty"`
ID string `json:"id,omitempty"`
CustomID string `json:"custom_id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
SellerProtection *SellerProtection `json:"seller_protection,omitempty"`
SellerReceivableBreakdown *SellerReceivableBreakdown `json:"seller_receivable_breakdown,omitempty"`
ID string `json:"id,omitempty"`
CustomID string `json:"custom_id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
}
// CapturedPayments has the amounts for a captured order
CapturedPayments struct {
Autthorizations []Authorization `json:"authorizations,omitempty"`
Captures []CaptureAmount `json:"captures,omitempty"`
Captures []CaptureAmount `json:"captures,omitempty"`
}
// CapturedPurchaseItem are items for a captured order
@ -968,13 +623,10 @@ type (
// PayerWithNameAndPhone struct
PayerWithNameAndPhone struct {
Name *CreateOrderPayerName `json:"name,omitempty"`
EmailAddress string `json:"email_address,omitempty"`
Phone *PhoneWithType `json:"phone,omitempty"`
PayerID string `json:"payer_id,omitempty"`
BirthDate string `json:"birth_date,omitempty"`
TaxInfo *TaxInfo `json:"tax_info,omitempty"`
Address *ShippingDetailAddressPortable `json:"address,omitempty"`
Name *CreateOrderPayerName `json:"name,omitempty"`
EmailAddress string `json:"email_address,omitempty"`
Phone *PhoneWithType `json:"phone,omitempty"`
PayerID string `json:"payer_id,omitempty"`
}
// CaptureOrderResponse is the response for capture order
@ -982,7 +634,6 @@ type (
ID string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
Payer *PayerWithNameAndPhone `json:"payer,omitempty"`
Address *Address `json:"address,omitempty"`
PurchaseUnits []CapturedPurchaseUnit `json:"purchase_units,omitempty"`
}
@ -1050,44 +701,27 @@ type (
// PaymentSource structure
PaymentSource struct {
Card *PaymentSourceCard `json:"card,omitempty"`
Token *PaymentSourceToken `json:"token,omitempty"`
Paypal *PaymentSourcePaypal `json:"paypal,omitempty"`
Card *PaymentSourceCard `json:"card"`
Token *PaymentSourceToken `json:"token"`
}
// PaymentSourceCard structure
PaymentSourceCard struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Number string `json:"number,omitempty"`
Expiry string `json:"expiry,omitempty"`
SecurityCode string `json:"security_code,omitempty"`
LastDigits string `json:"last_digits,omitempty"`
CardType string `json:"card_type,omitempty"`
BillingAddress *CardBillingAddress `json:"billing_address,omitempty"`
}
// PaymentSourcePaypal structure
PaymentSourcePaypal struct {
ExperienceContext PaymentSourcePaypalExperienceContext `json:"experience_context"`
}
PaymentSourcePaypalExperienceContext struct {
PaymentMethodPreference string `json:"payment_method_preference"`
BrandName string `json:"brand_name"`
Locale string `json:"locale"`
LandingPage string `json:"landing_page"`
ShippingPreference string `json:"shipping_preference"`
UserAction string `json:"user_action"`
ReturnURL string `json:"return_url"`
CancelURL string `json:"cancel_url"`
ID string `json:"id"`
Name string `json:"name"`
Number string `json:"number"`
Expiry string `json:"expiry"`
SecurityCode string `json:"security_code"`
LastDigits string `json:"last_digits"`
CardType string `json:"card_type"`
BillingAddress *CardBillingAddress `json:"billing_address"`
}
// CardBillingAddress structure
CardBillingAddress struct {
AddressLine1 string `json:"address_line_1"`
AddressLine2 string `json:"address_line_2,omitempty"`
AdminArea2 string `json:"admin_area_2,omitempty"`
AddressLine2 string `json:"address_line_2"`
AdminArea2 string `json:"admin_area_2"`
AdminArea1 string `json:"admin_area_1"`
PostalCode string `json:"postal_code"`
CountryCode string `json:"country_code"`
@ -1107,12 +741,11 @@ type (
// PayoutItem struct
PayoutItem struct {
RecipientType string `json:"recipient_type"`
RecipientWallet string `json:"recipient_wallet"`
Receiver string `json:"receiver"`
Amount *AmountPayout `json:"amount"`
Note string `json:"note,omitempty"`
SenderItemID string `json:"sender_item_id,omitempty"`
RecipientType string `json:"recipient_type"`
Receiver string `json:"receiver"`
Amount *AmountPayout `json:"amount"`
Note string `json:"note,omitempty"`
SenderItemID string `json:"sender_item_id,omitempty"`
}
// PayoutItemResponse struct
@ -1150,7 +783,6 @@ type (
CaptureID string `json:"capture_id,omitempty"`
ParentPayment string `json:"parent_payment,omitempty"`
UpdateTime *time.Time `json:"update_time,omitempty"`
SaleID string `json:"sale_id,omitempty"`
}
// RefundResponse .
@ -1158,7 +790,6 @@ type (
ID string `json:"id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
Status string `json:"status,omitempty"`
Links []Link `json:"links,omitempty"`
}
// Related struct
@ -1198,7 +829,8 @@ type (
//ShippingAmount struct
ShippingAmount struct {
Money
CurrencyCode string `json:"currency_code,omitempty"`
Value string `json:"value,omitempty"`
}
// ShippingAddress struct
@ -1225,14 +857,8 @@ type (
}
// Name struct
//Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-name
Name struct {
FullName string `json:"full_name,omitempty"`
Suffix string `json:"suffix,omitempty"`
Prefix string `json:"prefix,omitempty"`
GivenName string `json:"given_name,omitempty"`
Surname string `json:"surname,omitempty"`
MiddleName string `json:"middle_name,omitempty"`
FullName string `json:"full_name,omitempty"`
}
// ShippingDetail struct
@ -1243,7 +869,6 @@ type (
// Subscriber struct
Subscriber struct {
PayerID string `json:"payer_id"`
ShippingAddress ShippingDetail `json:"shipping_address,omitempty"`
Name CreateOrderPayerName `json:"name,omitempty"`
EmailAddress string `json:"email_address,omitempty"`
@ -1350,11 +975,7 @@ type (
VerificationStatus string `json:"verification_status,omitempty"`
}
WebhookEventTypesResponse struct {
EventTypes []WebhookEventType `json:"event_types"`
}
// Webhook struct
// Webhook strunct
Webhook struct {
ID string `json:"id"`
URL string `json:"url"`
@ -1362,31 +983,23 @@ type (
Links []Link `json:"links"`
}
// Event struct.
//
// The basic webhook event data type. This struct is intended to be
// embedded into resource type specific event structs.
Event struct {
// WebhookEvent struct
WebhookEvent struct {
ID string `json:"id"`
CreateTime time.Time `json:"create_time"`
ResourceType string `json:"resource_type"`
EventType string `json:"event_type"`
Summary string `json:"summary,omitempty"`
Resource Resource `json:"resource"`
Links []Link `json:"links"`
EventVersion string `json:"event_version,omitempty"`
ResourceVersion string `json:"resource_version,omitempty"`
}
AnyEvent struct {
Event
Resource json.RawMessage `json:"resource"`
}
// WebhookEventType struct
WebhookEventType struct {
Name string `json:"name"`
Description string `json:"description"`
Status string `json:"status,omitempty"`
}
// CreateWebhookRequest struct
@ -1405,6 +1018,26 @@ type (
Value interface{} `json:"value"`
}
Resource struct {
// Payment Resource type
ID string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
StatusDetails *CaptureStatusDetails `json:"status_details,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
UpdateTime string `json:"update_time,omitempty"`
CreateTime string `json:"create_time,omitempty"`
ExpirationTime string `json:"expiration_time,omitempty"`
SellerProtection *SellerProtection `json:"seller_protection,omitempty"`
FinalCapture bool `json:"final_capture,omitempty"`
SellerPayableBreakdown *CaptureSellerBreakdown `json:"seller_payable_breakdown,omitempty"`
NoteToPayer string `json:"note_to_payer,omitempty"`
// merchant-onboarding Resource type
PartnerClientID string `json:"partner_client_id,omitempty"`
MerchantID string `json:"merchant_id,omitempty"`
// Common
Links []Link `json:"links,omitempty"`
}
CaptureSellerBreakdown struct {
GrossAmount PurchaseUnitAmount `json:"gross_amount"`
PayPalFee PurchaseUnitAmount `json:"paypal_fee"`
@ -1420,10 +1053,6 @@ type (
LegalConsents []Consent `json:"legal_consents,omitempty"`
}
ReferralResponse struct {
Links []Link `json:"links,omitempty"`
}
PartnerConfigOverride struct {
PartnerLogoURL string `json:"partner_logo_url,omitempty"`
ReturnURL string `json:"return_url,omitempty"`
@ -1555,30 +1184,6 @@ type (
ShippingInfo *SearchShippingInfo `json:"shipping_info"`
CartInfo *SearchCartInfo `json:"cart_info"`
}
SharedResponse struct {
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
Links []Link `json:"links"`
}
ListParams struct {
Page string `json:"page,omitempty"` //Default: 0.
PageSize string `json:"page_size,omitempty"` //Default: 10.
TotalRequired string `json:"total_required,omitempty"` //Default: no.
}
SharedListResponse struct {
TotalItems int `json:"total_items,omitempty"`
TotalPages int `json:"total_pages,omitempty"`
Links []Link `json:"links,omitempty"`
}
Patch struct {
Operation string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
)
// Error method implementation for ErrorResponse struct
@ -1613,9 +1218,3 @@ func (e *expirationTime) UnmarshalJSON(b []byte) error {
*e = expirationTime(i)
return nil
}
// Convert ExpirationTime to time.Duration
func (e *expirationTime) ToDuration() time.Duration {
seconds := int64(*e)
return time.Duration(seconds) * time.Second
}

View File

@ -2,9 +2,16 @@ package paypal
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 {
@ -133,35 +140,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":{
@ -282,98 +260,6 @@ func TestOrderUnmarshal(t *testing.T) {
}
}
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 +357,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,14 +1,13 @@
package paypal
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
}
@ -24,8 +23,8 @@ func (c *Client) StoreCreditCard(ctx context.Context, cc CreditCard) (*CreditCar
// 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,8 +38,8 @@ 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
}
@ -56,7 +55,7 @@ func (c *Client) GetCreditCard(ctx context.Context, id string) (*CreditCard, err
// 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,7 +65,7 @@ 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
}
@ -82,8 +81,8 @@ func (c *Client) GetCreditCards(ctx context.Context, ccf *CreditCardsFilter) (*C
// 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
}

View File

@ -2,18 +2,16 @@ package paypal
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"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)
func (c *Client) CreateWebhook(createWebhookRequest *CreateWebhookRequest) (*Webhook, error) {
req, err := c.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), createWebhookRequest)
webhook := &Webhook{}
if err != nil {
return webhook, err
@ -25,8 +23,8 @@ func (c *Client) CreateWebhook(ctx context.Context, createWebhookRequest *Create
// 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)
func (c *Client) GetWebhook(webhookID string) (*Webhook, error) {
req, err := c.NewRequest(http.MethodGet, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/notifications/webhooks/", webhookID), nil)
webhook := &Webhook{}
if err != nil {
return webhook, err
@ -38,8 +36,8 @@ func (c *Client) GetWebhook(ctx context.Context, webhookID string) (*Webhook, er
// 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)
func (c *Client) UpdateWebhook(webhookID string, fields []WebhookField) (*Webhook, error) {
req, err := c.NewRequest(http.MethodPatch, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), fields)
webhook := &Webhook{}
if err != nil {
return webhook, err
@ -51,11 +49,11 @@ func (c *Client) UpdateWebhook(ctx context.Context, webhookID string, fields []W
// ListWebhooks - Lists webhooks for an app.
// Endpoint: GET /v1/notifications/webhooks
func (c *Client) ListWebhooks(ctx context.Context, anchorType string) (*ListWebhookResponse, error) {
func (c *Client) ListWebhooks(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)
req, err := c.NewRequest(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()
@ -70,8 +68,8 @@ func (c *Client) ListWebhooks(ctx context.Context, anchorType string) (*ListWebh
// 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)
func (c *Client) DeleteWebhook(webhookID string) error {
req, err := c.NewRequest(http.MethodDelete, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), nil)
if err != nil {
return err
}
@ -82,7 +80,7 @@ func (c *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
// 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) {
func (c *Client) VerifyWebhookSignature(httpReq *http.Request, webhookID string) (*VerifyWebhookResponse, error) {
type verifyWebhookSignatureRequest struct {
AuthAlgo string `json:"auth_algo,omitempty"`
CertURL string `json:"cert_url,omitempty"`
@ -90,18 +88,16 @@ func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Reque
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"`
WebhookEvent json.RawMessage `json:"webhook_event"`
}
// 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")
bodyBytes, _ = ioutil.ReadAll(httpReq.Body)
}
// Restore the io.ReadCloser to its original state
httpReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
httpReq.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
verifyRequest := verifyWebhookSignatureRequest{
AuthAlgo: httpReq.Header.Get("PAYPAL-AUTH-ALGO"),
@ -110,12 +106,12 @@ func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Reque
TransmissionSig: httpReq.Header.Get("PAYPAL-TRANSMISSION-SIG"),
TransmissionTime: httpReq.Header.Get("PAYPAL-TRANSMISSION-TIME"),
WebhookID: webhookID,
Event: json.RawMessage(bodyBytes),
WebhookEvent: json.RawMessage(bodyBytes),
}
response := &VerifyWebhookResponse{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/verify-webhook-signature"), verifyRequest)
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/verify-webhook-signature"), verifyRequest)
if err != nil {
return nil, err
}
@ -126,19 +122,3 @@ func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Reque
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
import (
"context"
"fmt"
"net/http"
)
@ -11,9 +10,9 @@ 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)
req, err := c.NewRequest("POST", url, wp)
response := &WebProfile{}
if err != nil {
@ -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
@ -51,14 +50,14 @@ func (c *Client) GetWebProfile(ctx context.Context, profileID string) (*WebProfi
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,7 +73,7 @@ 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")
@ -82,7 +81,7 @@ func (c *Client) SetWebProfile(ctx context.Context, wp WebProfile) error {
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