Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: plutov/paypal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.0.4
Choose a base ref
...
head repository: plutov/paypal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Loading
Showing with 2,810 additions and 1,087 deletions.
  1. +4 −0 .github/PULL_REQUEST_TEMPLATE.md
  2. +40 −0 .github/workflows/lint-test.yaml
  3. +0 −8 .travis.yml
  4. +10 −0 CONTRIBUTING.md
  5. +1 −1 LICENSE.md
  6. +97 −83 README.md
  7. +29 −13 authorization.go
  8. +0 −133 billing.go
  9. +48 −43 client.go
  10. +94 −0 client_test.go
  11. +118 −0 const.go
  12. +0 −17 example_test.go
  13. +10 −2 go.mod
  14. +10 −0 go.sum
  15. +3 −51 identity.go
  16. +0 −134 integration_test.go
  17. +38 −0 invoicing.go
  18. +406 −0 invoicing_test.go
  19. +122 −20 order.go
  20. +10 −9 payout.go
  21. +119 −0 products.go
  22. +6 −44 sale.go
  23. +234 −0 subscription.go
  24. +233 −0 subscription_plan.go
  25. +96 −0 transaction_search.go
  26. +839 −102 types.go
  27. +123 −399 unit_test.go
  28. +11 −10 vault.go
  29. +97 −7 webhooks.go
  30. +12 −11 webprofile.go
4 changes: 4 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#### What does this PR do?
#### Where should the reviewer start?
#### How should this be manually tested?
#### Any background context you want to provide?
40 changes: 40 additions & 0 deletions .github/workflows/lint-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Lint and Test

on: push

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 ./...
8 changes: 0 additions & 8 deletions .travis.yml

This file was deleted.

10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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.
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2019 Aliaksandr Pliutau
Copyright (c) 2024 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
180 changes: 97 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,31 @@
[![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)

### Go client for PayPal REST API

Currently supports **v2** only, if you want to use **v1**, use **v1.1.4** git tag.

### Coverage

* 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/authorization/**ID**
* POST /v2/payments/authorization/**ID**/capture
* POST /v2/payments/authorization/**ID**/void
* POST /v2/payments/authorization/**ID**/reauthorize
* GET /v2/payments/sale/**ID**
* POST /v2/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
* POST /v2/payments/billing-plans
* PATCH /v2/payments/billing-plans/***ID***
* POST /v2/payments/billing-agreements
* POST /v2/payments/billing-agreements/***TOKEN***/agreement-execute

### 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**

### New Client
[Docs](https://pkg.go.dev/github.com/plutov/paypal)

```go
import "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

## Paypal REST API Docs

[Get started with PayPal REST APIs](https://developer.paypal.com/api/rest/)

// If using Go Modules
// import "github.com/plutov/paypal/v3"
## Missing endpoints

It is possible that some endpoints are missing in this client, but you can use built-in `paypal` functions to perform a request: `NewClient -> NewRequest -> SendWithAuth`

## Usage

```go
import "github.com/plutov/paypal/v4"

// 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")
@@ -84,21 +49,6 @@ 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
@@ -114,7 +64,11 @@ order, err := c.GetOrder("O-4J082351X3132253H")
### Create an Order

```go
order, err := c.CreateOrder(paypal.OrderIntentCapture, []paypal.PurchaseUnitRequest{paypal.PurchaseUnitRequest{ReferenceID: "ref-id", Amount: paypal.Amount{Total: "7.00", Currency: "USD"}}})
ctx := context.Background()
units := []paypal.PurchaseUnitRequest{}
source := &paypal.PaymentSource{}
appCtx := &paypal.ApplicationContext{}
order, err := c.CreateOrder(ctx, paypal.OrderIntentCapture, units, ource, appCtx)
```

### Update Order by ID
@@ -170,7 +124,7 @@ payout := paypal.Payout{
},
}

payoutResp, err := c.CreateSinglePayout(payout)
payoutResp, err := c.CreatePayout(payout)
```

### Get payout by ID
@@ -232,7 +186,6 @@ webprofiles, err := c.GetWebProfiles()
### Update web experience profile

```go

webprofile := WebProfile{
ID: "XP-CP6S-W9DY-96H8-MVN2",
Name: "Shop YeowZa! YeowZa! ",
@@ -279,19 +232,80 @@ c.GetCreditCard("CARD-ID-123")
c.GetCreditCards(nil)
```

### How to Contribute
### Webhooks

* Fork a repository
* Add/Fix something
* Check that tests are passing
* Create PR
```go
// Create a webhook
c.CreateWebhook(paypal.CreateWebhookRequest{
URL: "webhook URL",
EventTypes: []paypal.WebhookEventType{
paypal.WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
})

Current contributors:
// Update a registered webhook
c.UpdateWebhook("WebhookID", []paypal.WebhookField{
paypal.WebhookField{
Operation: "replace",
Path: "/event_types",
Value: []interface{}{
map[string]interface{}{
"name": "PAYMENT.SALE.REFUNDED",
},
},
},
})

// Get a registered webhook
c.GetWebhook("WebhookID")

// Delete a webhook
c.DeleteWebhook("WebhookID")

// List registered webhooks
c.ListWebhooks(paypal.AncorTypeApplication)
```

### Generate Next Invoice Number

```go
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
c.GenerateInvoiceNumber(ctx) // might return something like "0001" or "0010".
```

### Get Invoice Details by ID

```go
// the second argument is an ID, it should be valid
invoice, err := c.GetInvoiceDetails(ctx, "INV2-XFXV-YW42-ZANU-4F33")
```

- for now, we are yet to implement the ShowAllInvoices endpoint, so use the following cURL request for the same(this gives you the list of invoice-IDs for this customer)

```bash
curl -v -X GET https://api-m.sandbox.paypal.com/v2/invoicing/invoices?total_required=true \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <Token>"
```

- refer to the beginning of this Usage section for obtaining a Token.

## How to Contribute

- Fork a repository
- Add/Fix something
- Check that tests are passing
- Create PR

Main contributors:

- [Roopak Venkatakrishnan](https://github.com/roopakv)
- [Alex Pliutau](https://github.com/plutov)
- [Roopak Venkatakrishnan](https://github.com/roopakv)

### Tests
## Tests

* Unit tests: `go test -v ./...`
* Integration tests: `go test -tags=integration`
```
go test -v ./...
```
42 changes: 29 additions & 13 deletions authorization.go
Original file line number Diff line number Diff line change
@@ -2,15 +2,16 @@ package paypal

import (
"bytes"
"context"
"fmt"
"net/http"
)

// GetAuthorization returns an authorization by ID
// Endpoint: GET /v2/payments/authorization/ID
func (c *Client) GetAuthorization(authID string) (*Authorization, error) {
// Endpoint: GET /v2/payments/authorizations/ID
func (c *Client) GetAuthorization(ctx context.Context, authID string) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(""))
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/payments/authorization/", authID), buf)
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/payments/authorizations/", authID), buf)
auth := &Authorization{}

if err != nil {
@@ -24,23 +25,38 @@ func (c *Client) GetAuthorization(authID string) (*Authorization, error) {
// 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(authID string, paymentCaptureRequest *PaymentCaptureRequest) (*PaymentCaptureResponse, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/capture"), paymentCaptureRequest)
func (c *Client) CaptureAuthorization(ctx context.Context, authID string, paymentCaptureRequest *PaymentCaptureRequest) (*PaymentCaptureResponse, error) {
return c.CaptureAuthorizationWithPaypalRequestId(ctx, authID, paymentCaptureRequest, "")
}

// CaptureAuthorization captures and process an existing authorization with idempotency.
// To use this method, the original payment must have Intent set to "authorize"
// Endpoint: POST /v2/payments/authorizations/ID/capture
func (c *Client) CaptureAuthorizationWithPaypalRequestId(ctx context.Context,
authID string,
paymentCaptureRequest *PaymentCaptureRequest,
requestID string,
) (*PaymentCaptureResponse, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/capture"), paymentCaptureRequest)
paymentCaptureResponse := &PaymentCaptureResponse{}

if err != nil {
return paymentCaptureResponse, err
}

if requestID != "" {
req.Header.Set("PayPal-Request-Id", requestID)
}

err = c.SendWithAuth(req, paymentCaptureResponse)
return paymentCaptureResponse, err
}

// VoidAuthorization voids a previously authorized payment
// Endpoint: POST /v2/payments/authorization/ID/void
func (c *Client) VoidAuthorization(authID string) (*Authorization, error) {
// Endpoint: POST /v2/payments/authorizations/ID/void
func (c *Client) VoidAuthorization(ctx context.Context, authID string) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(""))
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorization/"+authID+"/void"), buf)
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/void"), buf)
auth := &Authorization{}

if err != nil {
@@ -52,11 +68,11 @@ func (c *Client) VoidAuthorization(authID string) (*Authorization, error) {
}

// ReauthorizeAuthorization reauthorize a Paypal account payment.
// PayPal recommends to reauthorize payment after ~3 days
// Endpoint: POST /v2/payments/authorization/ID/reauthorize
func (c *Client) ReauthorizeAuthorization(authID string, a *Amount) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(`{"amount":{"currency":"` + a.Currency + `","total":"` + a.Total + `"}}`))
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorization/"+authID+"/reauthorize"), buf)
// PayPal recommends reauthorizing payment after ~3 days
// Endpoint: POST /v2/payments/authorizations/ID/reauthorize
func (c *Client) ReauthorizeAuthorization(ctx context.Context, authID string, a *Amount) (*Authorization, error) {
buf := bytes.NewBuffer([]byte(`{"amount":{"currency_code":"` + a.Currency + `","value":"` + a.Total + `"}}`))
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/reauthorize"), buf)
auth := &Authorization{}

if err != nil {
Loading