Compare commits

...

100 Commits

Author SHA1 Message Date
5c4c0e1b7b Merge pull request 'staging' (#5) from staging into master
Reviewed-on: #5
2024-10-22 18:42:28 +02:00
63bbbdf1e1 Update events to trigger actions lint-test.yaml 2024-10-22 18:42:28 +02:00
283485068c Merge pull request 'staging' (#3) from staging into master
All checks were successful
Lint and Test / lint (push) Successful in 10m22s
Lint and Test / test (push) Successful in 10m5s
Reviewed-on: #3
2024-10-22 16:37:21 +02:00
fb37ee85f2 Update package name 2024-10-22 16:37:21 +02:00
f840b7aac7
Add omitempty to JSON tags to CardBillingAddress AddressLine2 and AdminArea2
All checks were successful
Lint and Test / lint (push) Successful in 10m22s
Lint and Test / test (push) Successful in 10m6s
2024-10-22 14:24:40 +02:00
6217511bbd
Add omitempty to JSON tags to PaymentSourceCard type 2024-10-22 14:23:56 +02:00
27ffa97190
Add vendor directory to .gitignore 2024-10-22 14:22:47 +02:00
Alex Pliutau
0a5a40c12c
Create CONTRIBUTING.md
Some checks failed
Lint and Test / test (push) Waiting to run
Lint and Test / lint (push) Has been cancelled
2024-09-24 11:51:26 +02:00
Alex Pliutau
c9c1a38641 Remove unused tokenservice endpoints 2024-08-30 13:57:08 +02:00
Suhail Gupta
9451befb2b
Update README.md (#273) 2024-08-30 13:46:26 +02:00
Penny
daabe58f88
fix: Log request body (#272) 2024-08-19 13:24:58 +02:00
Alex Pliutau
5052fd4286 Fix #271 2024-07-10 10:57:51 +02:00
Alex Pliutau
3713084b64 go 1.21 2024-06-03 10:17:12 +02:00
Alex Pliutau
bfdf8e6e97 #263 2024-06-03 10:09:31 +02:00
Alex Pliutau
6ebb0352bc Remove deprecated endpoints 2024-06-03 09:53:55 +02:00
Alex Pliutau
e5db87f4a3 #260: rework SubscriptionBase struct 2024-05-27 12:44:13 +02:00
Seyed Hossein Hosseini Motlagh
cd3344edd1
Adds the ToDuration function to the paypalExpirationTime type. This function allows easy conversion of the internal value to a time.Duration type, which is crucial when using paypalExpirationTime values with Redis expiration times. (#265) 2024-05-27 12:36:56 +02:00
Alex Pliutau
b8f2c8c573 Use github actions 2024-05-27 12:36:25 +02:00
Penny
5fddf59f71
feat: get access token automatically (#261) 2024-02-01 10:07:34 +01:00
Albert Putra Purnama
26473c3630
Fixes test case (#259) 2023-10-04 22:31:48 +02:00
ihipop
ebf596676e
bring back landing_page option since commit #d355a65df01285c6aa4003099f8bfcb1a48ab517 (#251) 2022-11-18 15:34:38 +01:00
Akshay Prabhakant
4c16ffad0a
#177 feature/Invoice: init, added Invoice related structs, implemented met… (#249)
* feature/Invoice: init, added Invoice related structs, implemented methods:GenerateInvoiceNumber,GetInvoiceDetails(by ID)

* feature/Invoice: added implemented endpoints to README along with Usage

Co-authored-by: Akshay Prabhakant <akshayprabhakant@Akshays-MacBook-Pro.local>
2022-10-28 09:20:28 +02:00
Akshay Prabhakant
1bb626d559
Feature/private mutex (#248)
* feat/mutexPrivate:init; trying whether changes get pushed

* feat/privatedMutex: privated the mutex as a field in Client struct, added tests for this private mutex field

* feature/privateMutex: commented the composition of sync.Mutex

* feature/privateMutex: resolved issues with user credentials

Co-authored-by: Akshay Prabhakant <akshayprabhakant@Akshays-MacBook-Pro.local>
2022-10-20 09:11:49 +02:00
felipe.fuerback
f0575ee562
Refactor/code improvements (#247)
* Code improvements

* Revert log validation improvement
2022-10-07 22:07:27 +02:00
Binit kumar singh
defe3e09a4
Fix authorize order to return the correct response (#243) 2022-07-22 10:48:14 +02:00
Christopher Lorke
efc9bbdee0
Update types.go (#244)
Add missing field
2022-07-22 10:47:53 +02:00
Naoki Ikeguchi
b7d3cfd093
feat: Add missing fields into ErrorResponseDetail (#242)
* feat: Add missing fields into ErrorResponseDetail

* test: Add the example response as an unit test
2022-07-11 22:13:11 -07:00
Konnor Klashinsky
d94b350a31
add sale_id field to Refund struct (#238) 2022-06-18 22:04:53 +02:00
Konnor Klashinsky
48f8ee15ce
add payer_id field to Subscriber struct (#239) 2022-06-18 22:04:38 +02:00
Andy
7dc1f997d6
fix update order patchrequest (#240)
Co-authored-by: andy <xxxandyfenxxx@xxx.com>
2022-06-18 22:04:14 +02:00
maxzhao-bolt
c9ae5c4190
Adding PaymentMethod support into ApplicationContext (#237) 2022-03-17 11:33:06 -07:00
bartek1912
5c5393bd47
feat: add missing plan override (#229) 2022-03-05 08:16:50 +01:00
Florian Kinder
d381642422
Added PayPal-Mock-Response for capture (#232) 2022-03-05 08:16:25 +01:00
Anooj Muttavarapu
ca6845e257
Fixes updaterequest (#236)
* Fixes updaterequest

Fixes update request to support the patch request body format. https://developer.paypal.com/api/orders/v2/#definition-patch

* Change return type

Return type should only be error.

* Add nil return

Add nil return
2022-03-05 08:15:53 +01:00
uwevil
fc3ffe5b60
fix typing close #234 (#235) 2022-02-25 09:08:14 +01:00
Alex Pliutau
bf9103c70e
Merge pull request #231 from madprogramer/master
Potential fix for Issue 230
2021-11-11 12:48:32 +01:00
Ahmet Akkoç
bf0c207b52
Added spaces
`} else {`
2021-11-10 23:33:48 +01:00
Ahmet Akkoç
28f2333770
Fixed else syntax 2021-11-08 10:21:28 +01:00
Ahmet Akkoç
d815f6d8d9 Potential fix for Issue 230 2021-11-07 20:37:12 +01:00
Alex Pliutau
1a2c109908
Merge pull request #227 from emead/master
Add status field to CaptureAmount
2021-10-05 13:32:59 +02:00
Edwin Mead
50acd8fce1 Add status field to CaptureAmount 2021-10-04 15:01:28 +01:00
Nik Bisht
64bfbc9b80
Update ReAuthorization call parameters (#226) 2021-09-27 20:34:39 -07:00
Alex Pliutau
6fb262a2b0 License 2021 2021-09-17 08:45:25 +02:00
Alex Pliutau
7599a2d162 #201: add SUBSCRIBE_NOW 2021-09-17 08:44:39 +02:00
Alex Pliutau
ef48df5471
Merge pull request #223 from Yuusuke8686/fix_#219
Fix Reauthorize authorized payment parameter #219
2021-09-17 08:41:32 +02:00
Alex Pliutau
5a5daa8596
Merge pull request #224 from TariqueNasrullah/master
Grammatical error and Typo fixed in comments
2021-09-17 08:41:10 +02:00
Alex Pliutau
cbabac29c8
Merge pull request #225 from RensTillmann/patch-1
Added missing `items` on `PurchaseUnit` struct
2021-09-17 08:40:48 +02:00
Rens Tillmann
e6a47fe5e7
Added missing items on PurchaseUnit struct
When you call `GetOrder()` paypal also returns the items for `purchase_unit` ref: https://developer.paypal.com/docs/api/orders/v2/#definition-purchase_unit
The `PurchaseUnit` struct was missing this parameter.
2021-09-17 00:56:14 +02:00
TariqueNasrullah
59e3c9f3b0 fixed typo 2021-09-13 00:54:22 +06:00
TariqueNasrullah
326d69cc9e fixed grammatical error 2021-09-13 00:53:49 +06:00
Yuusuke Miyatake
4ab23e5f78 #219: fix parameter 2021-08-25 00:05:57 +09:00
rohanr-bolt
06067823c6
Add cancel billing agreement function (#222) 2021-08-19 10:29:00 -07:00
kenbolt
d5cba40cd6
Adding partner referral response (#217)
* Adding links to refund response

* Adding partner referral response
2021-07-27 15:56:29 -07:00
Cameron Jarnot
c2074af736
Update payment source type for optional fields (#216)
* updated payment source fields

* revert

* update payment source type
2021-07-27 11:59:36 -07:00
Dario Nevistić
297b4fa2c6
Updating Order with missing fields (#215)
* Update Order struct with API /v2 response fields according to the documentation https://developer.paypal.com/docs/api/orders/v2/\#orders_get

* Add order completed unmarshal response  unit test
2021-07-27 16:37:20 +02:00
kenbolt
8e5d5220ab
Adding links to refund response (#213) 2021-07-26 16:13:23 -07:00
Cameron Jarnot
2d088532de
Re-adding billing agreements endpoint (#212)
* adding billing_agreements.go file

* added new types

* added tests

* added notice of deprecation
2021-07-21 07:53:38 +02:00
Cameron Jarnot
f195993596
deletions made (#209) 2021-07-20 08:37:55 +02:00
Thomas Frössman
d2210adf9c
Convert depracation comments to godoc and deprecate Resource struct. (#207)
* Fix deprecation comment formatting for godoc.

* Deprecate Resource struct
2021-07-20 08:37:23 +02:00
Gerasimovich Igor
7f2eec9b56
Added missing BillingAgreementId key in webhook (#205)
* Added missing BillingAgreementId key in webhook

Webhook resource contains `billing_agreement_id`. Exists in type: `PAYMENT.SALE.COMPLETED` when a subscription is paid.

* review fix
2021-07-14 09:08:11 +02:00
Cameron Jarnot
392c0e9d69
Add create billing agreement from token (#204)
* Add create billing agreement from token
2021-07-13 10:14:56 -07:00
Cameron Jarnot
a3977a8e74
Adding functions for requesting billing agreement token (#203)
* created billing agreements file

* CreateBillingAgreementToken created with new types

* updated formatting

* error to return nil

* removed new line

* added test
2021-07-12 11:25:19 +02:00
Alex Pliutau
021cc68201
Merge pull request #199 from thomasf/remove-invalid-events
remove invalid event structs
2021-04-30 08:07:26 +02:00
Thomas Frössman
bbd66c6089 remove invalid event structs 2021-04-29 22:11:13 +02:00
Alex Pliutau
41099f6b6e fix typo in PaymentInstruction 2021-04-10 18:17:20 +02:00
Alex Pliutau
ed294ef317 #160: add PAY_NOW user action 2021-04-10 18:16:14 +02:00
Alex Pliutau
d90cb75e47 #163: payment_instruction on the purchase_unit_request 2021-04-10 18:13:45 +02:00
Alex Pliutau
62598e5880 #188: GetCapturedPaymentDetails 2021-04-10 18:09:05 +02:00
Alex Pliutau
52acc61786 #192: refactor Event type 2021-04-10 17:56:52 +02:00
Alex Pliutau
1e23f8dd7d return examples 2021-04-10 17:49:27 +02:00
Alex Pliutau
36281c1526
Merge pull request #196 from erkin97/fix-omitempty
Fix omitempty struct tag on Product.Description
2021-04-10 17:41:09 +02:00
Alex Pliutau
0a85944fee
Merge pull request #194 from Yuusuke8686/fix_#188
Add capture detail interface
2021-04-10 17:40:44 +02:00
Erkin Matkaziev
f694e414cd
Fix omitempty struct tag on Product.Description 2021-02-19 19:28:59 +09:00
Yuusuke
a3c2eaa0d4 add capture detail interface 2021-02-07 00:56:29 +09:00
Yash Suresh Chandra
a5cff3c18c
idempotent create order api (#191) 2021-01-25 12:52:19 -08:00
Alex Pliutau
df9918548b update readme 2021-01-20 10:30:37 +01:00
Alex Pliutau
f807b7d046
Merge pull request #190 from thomasf/context
add context
2021-01-20 10:27:33 +01:00
Thomas Frössman
3b835ea26a deprecate a few inconsistently named things and but keep aliases. 2021-01-20 09:31:54 +01:00
Thomas Frössman
e83fd911e0 add GetWebhookEventTypes 2021-01-03 21:20:12 +01:00
Thomas Frössman
cc8b3cee69 add context support 2021-01-03 10:49:14 +01:00
Dimas Ragil T
0f9d1cca16
Add Revise Subscription (#187)
* Add function revise subscription

* Update README add revise subscription
2020-12-16 10:40:27 +01:00
方航
98cae62470
fix param error while query transactions of subscriptions (#186)
url needs start_time and end_time, and both fields remove timezome
2020-11-28 09:56:24 +01:00
方航
ef386ff32f
fix param error (#185)
fix param error
2020-11-26 13:35:49 +01:00
jrapoport
476102bb76
Order should include payments & purchase_units (#179)
* Order should include purchase_units

* PayerWithNameAndPhone needs Address for Order

* add SellerProtection to CaptureAmount

* add RecipientType(s)

* add RecipientType(s)

* BatchHeader.BatchStatus values
2020-11-17 10:59:42 +01:00
Jason Hord
13112c66e5
Adds SellerReceivableBreakdown and CustomID fields to the Resource type (#180)
* Adds SellerReceivableBreakdown and CustomID fields to the Resource type

* Adds EventCheckoutOrderApproved constant and Intent, PurchaseUnits, Payer fields to Resource type
2020-11-17 10:59:19 +01:00
Jason Hord
5508bfebaf
Adds EventCheckoutOrderApproved constant and Intent, PurchaseUnits, Payer fields to Resource type (#181) 2020-11-17 10:59:02 +01:00
Jason Hord
ced90e676f
Adds a CustomID field to the SubscriptionBase struct (#182) 2020-11-17 10:58:44 +01:00
Jason Hord
abd51d2823
Changes omitempty tagging on Product struct to match Paypal API requirements (#183) 2020-11-17 10:58:07 +01:00
yunuseskisan
d1e5575c35
Add support for SellerReceivableBreakdown (#178)
* Add support for SellerReceivableBreakdown

https://developer.paypal.com/docs/api/orders/v2/#orders_capture

* Update types.go

* Update types.go
2020-10-23 09:52:58 +02:00
John Landis
289b2669ef
Order payer information (#169)
* simplest change

* re-using existing type

Co-authored-by: John Landis <jlandis@bcorporation.net>
2020-09-16 15:30:17 -07:00
Moo
761c20a4ed
change SharedListResponse TotalItems, TotalPages from string to int; Error: json: cannot unmarshal number into Go struct field ListSubscriptionPlansResponse.total_items of type string (#166)
Co-authored-by: Moo <mri@robin-data.io>
2020-08-25 09:20:11 -07:00
Onezino Moreira
18b47a0652
add recipient_wallet payout field (#165) 2020-08-13 16:20:35 -07:00
Rami
a0d03ecb0d
Fixes response from CreateSubscription (#155) 2020-06-01 11:02:18 -07:00
Alex Pliutau
905bf2eaf2 remove workflows 2020-05-31 18:18:05 +02:00
Alex Pliutau
a678ccb4b1
Remove examples, keep godoc as source of truth (#154) 2020-05-31 17:02:56 +02:00
Alex Pliutau
e84c6d06c5 Remove confusing message 2020-05-31 16:57:11 +02:00
Alex Pliutau
2e16e4fb10 auto-release workflow 2020-05-31 16:56:11 +02:00
Rami
0461b35d07
Fixed incorrect response type in ListSubscriptionPlans (#153)
* Update billing plan

* Update billing plan

* Update billing plan

* Kickstart product creation

* Added product api

* Added list product api and refactored some similar structs

* Reverted endpoint for old billing plans

* Added subscription plans create get update list

* Added subscription plans activate/deactivate/update pricing scheme

* Added integration tests for subscription plans

* go fmt

* Add subscriptions API

* Removed AWS library added by mistake

* reverted billing plans url

* resolved MR discussions

* Fixed but in listing subscription plans, and some constants

* Fixed wrong type for subscription plan description

* Fixed updating issue

Co-authored-by: rami <admin@okitoo.net>
2020-05-31 16:49:02 +02:00
Rami
d355a65df0
Subscriptions, Subscription plans and Products API (#151) 2020-05-30 22:14:19 -07:00
Roopak Venkatakrishnan
b3eb2c69dd
Revert "Some updates to refund endpoint (#121)" (#147)
This reverts commit efe72c1ed4.
2020-05-04 13:17:53 -07:00
31 changed files with 2221 additions and 1224 deletions

4
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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?

50
.github/workflows/lint-test.yaml vendored Normal file
View File

@ -0,0 +1,50 @@
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 ./...

1
.gitignore vendored
View File

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

View File

@ -1,8 +0,0 @@
language: go
go:
- 1.11
- 1.12
install:
- export PATH=$PATH:$HOME/gopath/bin
script:
- go test -v -race

10
CONTRIBUTING.md Normal file
View File

@ -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.

View File

@ -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

142
README.md
View File

@ -1,74 +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)
[Docs](https://pkg.go.dev/github.com/plutov/paypal)
### Go client for PayPal REST API
<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>
Currently supports **v2** only, if you want to use **v1**, use **v1.1.4** git tag.
# Go client for PayPal REST API
### Coverage
## Paypal REST API Docs
* 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
[Get started with PayPal REST APIs](https://developer.paypal.com/api/rest/)
### 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**
## Missing endpoints
### New Client
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"
// If using Go Modules
// import "github.com/plutov/paypal/v3"
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")
@ -92,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
@ -122,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
@ -178,7 +124,7 @@ payout := paypal.Payout{
},
}
payoutResp, err := c.CreateSinglePayout(payout)
payoutResp, err := c.CreatePayout(payout)
```
### Get payout by ID
@ -240,7 +186,6 @@ webprofiles, err := c.GetWebProfiles()
### Update web experience profile
```go
webprofile := WebProfile{
ID: "XP-CP6S-W9DY-96H8-MVN2",
Name: "Shop YeowZa! YeowZa! ",
@ -288,6 +233,7 @@ c.GetCreditCards(nil)
```
### Webhooks
```go
// Create a webhook
c.CreateWebhook(paypal.CreateWebhookRequest{
@ -320,22 +266,46 @@ c.DeleteWebhook("WebhookID")
// List registered webhooks
c.ListWebhooks(paypal.AncorTypeApplication)
```
### How to Contribute
### Generate Next Invoice Number
* Fork a repository
* Add/Fix something
* Check that tests are passing
* Create PR
```go
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
c.GenerateInvoiceNumber(ctx) // might return something like "0001" or "0010".
```
Current contributors:
### 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 ./...
```

View File

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

View File

@ -1,133 +0,0 @@
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() (*TokenResponse, error) {
func (c *Client) GetAccessToken(ctx context.Context) (*TokenResponse, error) {
buf := bytes.NewBuffer([]byte("grant_type=client_credentials"))
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/oauth2/token"), buf)
req, err := http.NewRequestWithContext(ctx, "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
// unmarshaled into v, or if v is an io.Writer, the response will
// unmarshalled 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,21 +97,36 @@ 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
}
defer resp.Body.Close()
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)
if resp.StatusCode < 200 || resp.StatusCode > 299 {
errResp := &ErrorResponse{Response: resp}
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
if err == nil && len(data) > 0 {
json.Unmarshal(data, errResp)
err := json.Unmarshal(data, errResp)
if err != nil {
return err
}
}
return errResp
@ -121,8 +136,8 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
}
if w, ok := v.(io.Writer); ok {
io.Copy(w, resp.Body)
return nil
_, err := io.Copy(w, resp.Body)
return err
}
return json.NewDecoder(resp.Body).Decode(v)
@ -133,25 +148,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.Lock()
c.mu.Lock()
// Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)`
// to happen outside of the locked section.
if c.Token != nil {
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
if c.Token == nil || (!c.tokenExpiresAt.IsZero() && time.Until(c.tokenExpiresAt) < RequestNewTokenBeforeExpiresIn) {
// c.Token will be updated in GetAccessToken call
if _, err := c.GetAccessToken(); err != nil {
c.Unlock()
if _, err := c.GetAccessToken(req.Context()); err != nil {
// c.Unlock()
c.mu.Unlock()
return err
}
}
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.Unlock()
c.mu.Unlock()
return c.Send(req, v)
}
@ -164,7 +179,7 @@ func (c *Client) SendWithBasicAuth(req *http.Request, v interface{}) error {
// NewRequest constructs a request
// Convert payload to a JSON
func (c *Client) NewRequest(method, url string, payload interface{}) (*http.Request, error) {
func (c *Client) NewRequest(ctx context.Context, method, url string, payload interface{}) (*http.Request, error) {
var buf io.Reader
if payload != nil {
b, err := json.Marshal(&payload)
@ -173,24 +188,5 @@ func (c *Client) NewRequest(method, url string, payload interface{}) (*http.Requ
}
buf = bytes.NewBuffer(b)
}
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))))
}
return http.NewRequestWithContext(ctx, method, url, buf)
}

94
client_test.go Normal file
View File

@ -0,0 +1,94 @@
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 Normal file
View File

@ -0,0 +1,118 @@
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"
)

View File

@ -1,17 +0,0 @@
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,3 +1,11 @@
module github.com/plutov/paypal/v3
module euphoria-laxis.fr/go-packages/paypale/v4
go 1.12
go 1.23
require github.com/stretchr/testify v1.9.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
go.sum
View File

@ -0,0 +1,10 @@
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,65 +1,17 @@
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>
// 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) {
func (c *Client) GetUserInfo(ctx context.Context, schema string) (*UserInfo, error) {
u := &UserInfo{}
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/identity/openidconnect/userinfo/?schema=", schema), nil)
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/identity/openidconnect/userinfo/?schema=", schema), nil)
if err != nil {
return u, err
}

View File

@ -1,214 +0,0 @@
// +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)
}
}

38
invoicing.go Normal file
View File

@ -0,0 +1,38 @@
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
}

406
invoicing_test.go Normal file
View File

@ -0,0 +1,406 @@
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,13 +1,17 @@
package paypal
import "fmt"
import (
"context"
"encoding/json"
"fmt"
)
// GetOrder retrieves order by ID
// Endpoint: GET /v2/checkout/orders/ID
func (c *Client) GetOrder(orderID string) (*Order, error) {
func (c *Client) GetOrder(ctx context.Context, orderID string) (*Order, error) {
order := &Order{}
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), nil)
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), nil)
if err != nil {
return order, err
}
@ -19,23 +23,39 @@ func (c *Client) GetOrder(orderID string) (*Order, error) {
return order, nil
}
// CreateOrder - Use this call to create an order
// Create an order
// Endpoint: POST /v2/checkout/orders
func (c *Client) CreateOrder(intent string, purchaseUnits []PurchaseUnitRequest, payer *CreateOrderPayer, appContext *ApplicationContext) (*Order, error) {
func (c *Client) CreateOrder(ctx context.Context, intent string, purchaseUnits []PurchaseUnitRequest, paymentSource *PaymentSource, appContext *ApplicationContext) (*Order, error) {
return c.CreateOrderWithPaypalRequestID(ctx, intent, purchaseUnits, paymentSource, appContext, "")
}
// CreateOrderWithPaypalRequestID - Use this call to create an order with idempotency
// Endpoint: POST /v2/checkout/orders
func (c *Client) CreateOrderWithPaypalRequestID(ctx context.Context,
intent string,
purchaseUnits []PurchaseUnitRequest,
paymentSource *PaymentSource,
appContext *ApplicationContext,
requestID string,
) (*Order, error) {
type createOrderRequest struct {
Intent string `json:"intent"`
Payer *CreateOrderPayer `json:"payer,omitempty"`
PaymentSource *PaymentSource `json:"payment_source,omitempty"`
PurchaseUnits []PurchaseUnitRequest `json:"purchase_units"`
ApplicationContext *ApplicationContext `json:"application_context,omitempty"`
}
order := &Order{}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders"), createOrderRequest{Intent: intent, PurchaseUnits: purchaseUnits, Payer: payer, ApplicationContext: appContext})
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders"), createOrderRequest{Intent: intent, PurchaseUnits: purchaseUnits, PaymentSource: paymentSource, ApplicationContext: appContext})
if err != nil {
return order, err
}
if requestID != "" {
req.Header.Set("PayPal-Request-Id", requestID)
}
if err = c.SendWithAuth(req, order); err != nil {
return order, err
}
@ -45,27 +65,37 @@ func (c *Client) CreateOrder(intent string, purchaseUnits []PurchaseUnitRequest,
// UpdateOrder updates the order by ID
// Endpoint: PATCH /v2/checkout/orders/ID
func (c *Client) UpdateOrder(orderID string, purchaseUnits []PurchaseUnitRequest) (*Order, error) {
order := &Order{}
func (c *Client) UpdateOrder(ctx context.Context, orderID string, op string, path string, value map[string]string) error {
req, err := c.NewRequest("PATCH", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), purchaseUnits)
type patchRequest struct {
Op string `json:"op"`
Path string `json:"path"`
Value map[string]string `json:"value"`
}
req, err := c.NewRequest(ctx, "PATCH", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), []patchRequest{
{
Op: op,
Path: path,
Value: value,
},
})
if err != nil {
return order, err
return err
}
if err = c.SendWithAuth(req, order); err != nil {
return order, err
if err = c.SendWithAuth(req, nil); err != nil {
return err
}
return order, nil
return nil
}
// AuthorizeOrder - https://developer.paypal.com/docs/api/orders/v2/#orders_authorize
// Endpoint: POST /v2/checkout/orders/ID/authorize
func (c *Client) AuthorizeOrder(orderID string, authorizeOrderRequest AuthorizeOrderRequest) (*Authorization, error) {
auth := &Authorization{}
func (c *Client) AuthorizeOrder(ctx context.Context, orderID string, authorizeOrderRequest AuthorizeOrderRequest) (*AuthorizeOrderResponse, error) {
auth := &AuthorizeOrderResponse{}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/authorize"), authorizeOrderRequest)
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/authorize"), authorizeOrderRequest)
if err != nil {
return auth, err
}
@ -79,22 +109,23 @@ func (c *Client) AuthorizeOrder(orderID string, authorizeOrderRequest AuthorizeO
// CaptureOrder - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
// Endpoint: POST /v2/checkout/orders/ID/capture
func (c *Client) CaptureOrder(orderID string, captureOrderRequest CaptureOrderRequest) (*CaptureOrderResponse, error) {
return c.CaptureOrderWithPaypalRequestId(orderID, captureOrderRequest, "")
func (c *Client) CaptureOrder(ctx context.Context, orderID string, captureOrderRequest CaptureOrderRequest) (*CaptureOrderResponse, error) {
return c.CaptureOrderWithPaypalRequestId(ctx, orderID, captureOrderRequest, "", nil)
}
// 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(
func (c *Client) CaptureOrderWithPaypalRequestId(ctx context.Context,
orderID string,
captureOrderRequest CaptureOrderRequest,
requestID string,
mockResponse *CaptureOrderMockResponse,
) (*CaptureOrderResponse, error) {
capture := &CaptureOrderResponse{}
c.SetReturnRepresentation()
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/capture"), captureOrderRequest)
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/capture"), captureOrderRequest)
if err != nil {
return capture, err
}
@ -103,6 +134,15 @@ func (c *Client) CaptureOrderWithPaypalRequestId(
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
}
@ -112,20 +152,20 @@ func (c *Client) CaptureOrderWithPaypalRequestId(
// RefundCapture - https://developer.paypal.com/docs/api/payments/v2/#captures_refund
// Endpoint: POST /v2/payments/captures/ID/refund
func (c *Client) RefundCapture(captureID string, refundCaptureRequest RefundCaptureRequest) (*RefundResponse, error) {
return c.RefundCaptureWithPaypalRequestId(captureID, refundCaptureRequest, "")
func (c *Client) RefundCapture(ctx context.Context, captureID string, refundCaptureRequest RefundCaptureRequest) (*RefundResponse, error) {
return c.RefundCaptureWithPaypalRequestId(ctx, 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(
func (c *Client) RefundCaptureWithPaypalRequestId(ctx context.Context,
captureID string,
refundCaptureRequest RefundCaptureRequest,
requestID string,
) (*RefundResponse, error) {
refund := &RefundResponse{}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/captures/"+captureID+"/refund"), refundCaptureRequest)
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/captures/"+captureID+"/refund"), refundCaptureRequest)
if err != nil {
return refund, err
}
@ -139,3 +179,19 @@ func (c *Client) RefundCaptureWithPaypalRequestId(
}
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,14 +1,15 @@
package paypal
import (
"context"
"fmt"
)
// CreateSinglePayout submits a payout with an asynchronous API call, which immediately returns the results of a PayPal payment.
// CreatePayout 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) CreateSinglePayout(p Payout) (*PayoutResponse, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts"), p)
func (c *Client) CreatePayout(ctx context.Context, p Payout) (*PayoutResponse, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts"), p)
response := &PayoutResponse{}
if err != nil {
@ -25,8 +26,8 @@ func (c *Client) CreateSinglePayout(p Payout) (*PayoutResponse, error) {
// 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(payoutBatchID string) (*PayoutResponse, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts/"+payoutBatchID), nil)
func (c *Client) GetPayout(ctx context.Context, payoutBatchID string) (*PayoutResponse, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts/"+payoutBatchID), nil)
response := &PayoutResponse{}
if err != nil {
@ -43,8 +44,8 @@ func (c *Client) GetPayout(payoutBatchID string) (*PayoutResponse, error) {
// 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(payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID), nil)
func (c *Client) GetPayoutItem(ctx context.Context, payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID), nil)
response := &PayoutItemResponse{}
if err != nil {
@ -61,8 +62,8 @@ func (c *Client) GetPayoutItem(payoutItemID string) (*PayoutItemResponse, error)
// 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(payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID+"/cancel"), nil)
func (c *Client) CancelPayoutItem(ctx context.Context, payoutItemID string) (*PayoutItemResponse, error) {
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payouts-item/"+payoutItemID+"/cancel"), nil)
response := &PayoutItemResponse{}
if err != nil {

119
products.go Normal file
View File

@ -0,0 +1,119 @@
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
}

45
sale.go
View File

@ -1,50 +1,17 @@
package paypal
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.
func (c *Client) RefundSale(saleID string, r RefundRequest) (*Refund, error) {
refund := &Refund{}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/sale/"+saleID+"/refund"), &r)
if err != nil {
return refund, err
}
if err = c.SendWithAuth(req, refund); err != nil {
return refund, err
}
return refund, nil
}
import (
"context"
"fmt"
)
// 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) {
func (c *Client) GetRefund(ctx context.Context, refundID string) (*Refund, error) {
refund := &Refund{}
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/refund/"+refundID), nil)
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/refund/"+refundID), nil)
if err != nil {
return refund, err
}

View File

@ -1,33 +1,125 @@
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 {
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"`
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"`
}
)
// GetSubscriptionDetails shows details for a subscription, by ID.
// Endpoint: GET /v1/billing/subscriptions/
func (c *Client) GetSubscriptionDetails(subscriptionID string) (*SubscriptionDetailResp, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/billing/subscriptions/%s", c.APIBase, subscriptionID), nil)
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
@ -35,3 +127,108 @@ func (c *Client) GetSubscriptionDetails(subscriptionID string) (*SubscriptionDet
err = c.SendWithAuth(req, response)
return response, err
}
// UpdateSubscriptionPlan. updates a plan
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_patch
// Endpoint: PATCH /v1/billing/subscriptions/:subscription_id
func (c *Client) UpdateSubscription(ctx context.Context, updatedSubscription Subscription) error {
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/billing/subscriptions/", updatedSubscription.ID), updatedSubscription.GetUpdatePatch())
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// GetSubscriptionDetails shows details for a subscription, by ID.
// Endpoint: GET /v1/billing/subscriptions/
func (c *Client) GetSubscriptionDetails(ctx context.Context, subscriptionID string) (*SubscriptionDetailResp, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/billing/subscriptions/%s", c.APIBase, subscriptionID), nil)
response := &SubscriptionDetailResp{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// Activates the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_activate
// Endpoint: POST /v1/billing/subscriptions/{id}/activate
func (c *Client) ActivateSubscription(ctx context.Context, subscriptionId, activateReason string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/activate", c.APIBase, subscriptionId), map[string]string{"reason": activateReason})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Cancels the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
// Endpoint: POST /v1/billing/subscriptions/{id}/cancel
func (c *Client) CancelSubscription(ctx context.Context, subscriptionId, cancelReason string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/cancel", c.APIBase, subscriptionId), map[string]string{"reason": cancelReason})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Captures an authorized payment from the subscriber on the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_capture
// Endpoint: POST /v1/billing/subscriptions/{id}/capture
func (c *Client) CaptureSubscription(ctx context.Context, subscriptionId string, request CaptureRequest) (*SubscriptionCaptureResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/capture", c.APIBase, subscriptionId), request)
response := &SubscriptionCaptureResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// Suspends the subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_suspend
// Endpoint: POST /v1/billing/subscriptions/{id}/suspend
func (c *Client) SuspendSubscription(ctx context.Context, subscriptionId, reason string) error {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/suspend", c.APIBase, subscriptionId), map[string]string{"reason": reason})
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// Lists transactions for a subscription.
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_transactions
// Endpoint: GET /v1/billing/subscriptions/{id}/transactions
func (c *Client) GetSubscriptionTransactions(ctx context.Context, requestParams SubscriptionTransactionsParams) (*SubscriptionTransactionsResponse, error) {
startTime := requestParams.StartTime.Format("2006-01-02T15:04:05Z")
endTime := requestParams.EndTime.Format("2006-01-02T15:04:05Z")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/billing/subscriptions/%s/transactions?start_time=%s&end_time=%s", c.APIBase, requestParams.SubscriptionId, startTime, endTime), nil)
response := &SubscriptionTransactionsResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// Revise plan or quantity of subscription
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_revise
// Endpoint: POST /v1/billing/subscriptions/{id}/revise
func (c *Client) ReviseSubscription(ctx context.Context, subscriptionId string, reviseSubscription SubscriptionBase) (*SubscriptionDetailResp, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/revise", c.APIBase, subscriptionId), reviseSubscription)
response := &SubscriptionDetailResp{}
if err != nil {
return response, err
}
req.Header.Add("Content-Type", "application/json")
err = c.SendWithAuth(req, response)
return response, err
}

233
subscription_plan.go Normal file
View File

@ -0,0 +1,233 @@
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,6 +1,7 @@
package paypal
import (
"context"
"fmt"
"strconv"
"time"
@ -30,17 +31,15 @@ type TransactionSearchResponse struct {
EndDate JSONTime `json:"end_date"`
LastRefreshDatetime JSONTime `json:"last_refreshed_datetime"`
Page int `json:"page"`
TotalItems int `json:"total_items"`
TotalPages int `json:"total_pages"`
Links []Link `json:"links"`
SharedListResponse
}
// ListTransactions - Use this to search PayPal transactions from the last 31 days.
// Endpoint: GET /v1/reporting/transactions
func (c *Client) ListTransactions(req *TransactionSearchRequest) (*TransactionSearchResponse, error) {
func (c *Client) ListTransactions(ctx context.Context, req *TransactionSearchRequest) (*TransactionSearchResponse, error) {
response := &TransactionSearchResponse{}
r, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/reporting/transactions"), nil)
r, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/reporting/transactions"), nil)
if err != nil {
return nil, err
}

559
types.go
View File

@ -12,10 +12,10 @@ import (
const (
// APIBaseSandBox points to the sandbox (for testing) version of the API
APIBaseSandBox = "https://api.sandbox.paypal.com"
APIBaseSandBox = "https://api-m.sandbox.paypal.com"
// APIBaseLive points to the live version of the API
APIBaseLive = "https://api.paypal.com"
APIBaseLive = "https://api-m.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,13 +85,9 @@ 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"
@ -120,6 +116,31 @@ 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"
@ -136,10 +157,10 @@ type (
// Address struct
Address struct {
Line1 string `json:"line1"`
Line1 string `json:"line1,omitempty"`
Line2 string `json:"line2,omitempty"`
City string `json:"city"`
CountryCode string `json:"country_code"`
City string `json:"city,omitempty"`
CountryCode string `json:"country_code,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
State string `json:"state,omitempty"`
Phone string `json:"phone,omitempty"`
@ -171,16 +192,282 @@ 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"`
ShippingPreference string `json:"shipping_preference,omitempty"`
UserAction string `json:"user_action,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"`
}
// Authorization struct
Authorization struct {
ID string `json:"id,omitempty"`
@ -196,14 +483,13 @@ 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 []PurchaseUnitRequest `json:"purchase_units,omitempty"`
PurchaseUnits []PurchaseUnit `json:"purchase_units,omitempty"`
Payer *PayerWithNameAndPhone `json:"payer,omitempty"`
}
@ -234,12 +520,6 @@ type (
FinalCapture bool `json:"final_capture,omitempty"`
}
RefundRequest struct {
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
InvoiceID string `json:"invoice_id,omitempty"`
NoteToPayer string `json:"note_to_payer,omitempty"`
}
SellerProtection struct {
Status string `json:"status,omitempty"`
DisputeCategories []string `json:"dispute_categories,omitempty"`
@ -250,10 +530,6 @@ type (
Reason string `json:"reason,omitempty"`
}
RefundStatusDetails struct {
Reason string `json:"reason,omitempty"`
}
PaymentCaptureResponse struct {
Status string `json:"status,omitempty"`
StatusDetails *CaptureStatusDetails `json:"status_details,omitempty"`
@ -265,11 +541,33 @@ 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"`
@ -288,15 +586,14 @@ type (
SenderBatchHeader *SenderBatchHeader `json:"sender_batch_header,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"`
// 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"`
}
// BillingInfo struct
@ -320,13 +617,14 @@ 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"`
}
@ -338,7 +636,8 @@ type (
// Client represents a Paypal REST API Client
Client struct {
sync.Mutex
// sync.Mutex
mu sync.Mutex
Client *http.Client
ClientID string
Secret string
@ -369,9 +668,7 @@ type (
// CreditCards GET /v1/vault/credit-cards
CreditCards struct {
Items []CreditCard `json:"items"`
Links []Link `json:"links"`
TotalItems int `json:"total_items"`
TotalPages int `json:"total_pages"`
SharedListResponse
}
// CreditCardToken struct
@ -432,6 +729,9 @@ type (
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"`
}
@ -525,10 +825,19 @@ 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"`
}
// TaxInfo used for orders.
@ -576,6 +885,7 @@ type (
SoftDescriptor string `json:"soft_descriptor,omitempty"`
Items []Item `json:"items,omitempty"`
Shipping *ShippingDetail `json:"shipping,omitempty"`
PaymentInstruction *PaymentInstruction `json:"payment_instruction,omitempty"`
}
// MerchantPreferences struct
@ -593,21 +903,46 @@ type (
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"`
}
// 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"`
}
// CapturedPayments has the amounts for a captured order
CapturedPayments struct {
Autthorizations []Authorization `json:"authorizations,omitempty"`
Captures []CaptureAmount `json:"captures,omitempty"`
}
@ -637,6 +972,9 @@ type (
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"`
}
// CaptureOrderResponse is the response for capture order
@ -644,6 +982,7 @@ 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"`
}
@ -711,27 +1050,44 @@ type (
// PaymentSource structure
PaymentSource struct {
Card *PaymentSourceCard `json:"card"`
Token *PaymentSourceToken `json:"token"`
Card *PaymentSourceCard `json:"card,omitempty"`
Token *PaymentSourceToken `json:"token,omitempty"`
Paypal *PaymentSourcePaypal `json:"paypal,omitempty"`
}
// PaymentSourceCard structure
PaymentSourceCard struct {
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"`
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"`
}
// CardBillingAddress structure
CardBillingAddress struct {
AddressLine1 string `json:"address_line_1"`
AddressLine2 string `json:"address_line_2"`
AdminArea2 string `json:"admin_area_2"`
AddressLine2 string `json:"address_line_2,omitempty"`
AdminArea2 string `json:"admin_area_2,omitempty"`
AdminArea1 string `json:"admin_area_1"`
PostalCode string `json:"postal_code"`
CountryCode string `json:"country_code"`
@ -752,6 +1108,7 @@ 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"`
@ -787,15 +1144,13 @@ type (
// Refund struct
Refund struct {
ID string `json:"id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
Status string `json:"status,omitempty"`
StatusDetails *RefundStatusDetails `json:"status_details,omitempty"`
InvoiceID string `json:"invoice_id,omitempty"`
NoteToPayer string `json:"note_to_payer,omitempty"`
SellerPayableBreakdown *CaptureSellerBreakdown `json:"seller_payable_breakdown,omitempty"`
Links []Link `json:"links"`
Amount *Amount `json:"amount,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
State string `json:"state,omitempty"`
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 .
@ -803,6 +1158,7 @@ type (
ID string `json:"id,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
Status string `json:"status,omitempty"`
Links []Link `json:"links,omitempty"`
}
// Related struct
@ -842,8 +1198,7 @@ type (
//ShippingAmount struct
ShippingAmount struct {
CurrencyCode string `json:"currency_code,omitempty"`
Value string `json:"value,omitempty"`
Money
}
// ShippingAddress struct
@ -870,8 +1225,14 @@ 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"`
}
// ShippingDetail struct
@ -882,6 +1243,7 @@ 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"`
@ -988,7 +1350,11 @@ type (
VerificationStatus string `json:"verification_status,omitempty"`
}
// Webhook strunct
WebhookEventTypesResponse struct {
EventTypes []WebhookEventType `json:"event_types"`
}
// Webhook struct
Webhook struct {
ID string `json:"id"`
URL string `json:"url"`
@ -996,22 +1362,31 @@ type (
Links []Link `json:"links"`
}
WebhookEvent struct {
// Event struct.
//
// The basic webhook event data type. This struct is intended to be
// embedded into resource type specific event structs.
Event 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
@ -1030,26 +1405,6 @@ 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"`
@ -1065,6 +1420,10 @@ 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"`
@ -1196,6 +1555,30 @@ 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
@ -1230,3 +1613,9 @@ 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,16 +2,9 @@ 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 {
@ -140,6 +133,35 @@ 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":{
@ -260,6 +282,98 @@ 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",
@ -357,389 +471,3 @@ 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,13 +1,14 @@
package paypal
import (
"context"
"fmt"
)
// StoreCreditCard func
// Endpoint: POST /v1/vault/credit-cards
func (c *Client) StoreCreditCard(cc CreditCard) (*CreditCard, error) {
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/vault/credit-cards"), cc)
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)
if err != nil {
return nil, err
}
@ -23,8 +24,8 @@ func (c *Client) StoreCreditCard(cc CreditCard) (*CreditCard, error) {
// DeleteCreditCard func
// Endpoint: DELETE /v1/vault/credit-cards/credit_card_id
func (c *Client) DeleteCreditCard(id string) error {
req, err := c.NewRequest("DELETE", fmt.Sprintf("%s/v1/vault/credit-cards/%s", c.APIBase, id), nil)
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)
if err != nil {
return err
}
@ -38,8 +39,8 @@ func (c *Client) DeleteCreditCard(id string) error {
// GetCreditCard func
// Endpoint: GET /v1/vault/credit-cards/credit_card_id
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)
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)
if err != nil {
return nil, err
}
@ -55,7 +56,7 @@ func (c *Client) GetCreditCard(id string) (*CreditCard, error) {
// GetCreditCards func
// Endpoint: GET /v1/vault/credit-cards
func (c *Client) GetCreditCards(ccf *CreditCardsFilter) (*CreditCards, error) {
func (c *Client) GetCreditCards(ctx context.Context, ccf *CreditCardsFilter) (*CreditCards, error) {
page := 1
if ccf != nil && ccf.Page > 0 {
page = ccf.Page
@ -65,7 +66,7 @@ func (c *Client) GetCreditCards(ccf *CreditCardsFilter) (*CreditCards, error) {
pageSize = ccf.PageSize
}
req, err := c.NewRequest("GET", fmt.Sprintf("%s/v1/vault/credit-cards?page=%d&page_size=%d", c.APIBase, page, pageSize), nil)
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s/v1/vault/credit-cards?page=%d&page_size=%d", c.APIBase, page, pageSize), nil)
if err != nil {
return nil, err
}
@ -81,8 +82,8 @@ func (c *Client) GetCreditCards(ccf *CreditCardsFilter) (*CreditCards, error) {
// PatchCreditCard func
// Endpoint: PATCH /v1/vault/credit-cards/credit_card_id
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)
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)
if err != nil {
return nil, err
}

View File

@ -2,16 +2,18 @@ package paypal
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
)
// CreateWebhook - Subscribes your webhook listener to events.
// Endpoint: POST /v1/notifications/webhooks
func (c *Client) CreateWebhook(createWebhookRequest *CreateWebhookRequest) (*Webhook, error) {
req, err := c.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), createWebhookRequest)
func (c *Client) CreateWebhook(ctx context.Context, createWebhookRequest *CreateWebhookRequest) (*Webhook, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), createWebhookRequest)
webhook := &Webhook{}
if err != nil {
return webhook, err
@ -23,8 +25,8 @@ func (c *Client) CreateWebhook(createWebhookRequest *CreateWebhookRequest) (*Web
// GetWebhook - Shows details for a webhook, by ID.
// Endpoint: GET /v1/notifications/webhooks/ID
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)
func (c *Client) GetWebhook(ctx context.Context, webhookID string) (*Webhook, error) {
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/notifications/webhooks/", webhookID), nil)
webhook := &Webhook{}
if err != nil {
return webhook, err
@ -36,8 +38,8 @@ func (c *Client) GetWebhook(webhookID string) (*Webhook, error) {
// UpdateWebhook - Updates a webhook to replace webhook fields with new values.
// Endpoint: PATCH /v1/notifications/webhooks/ID
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)
func (c *Client) UpdateWebhook(ctx context.Context, webhookID string, fields []WebhookField) (*Webhook, error) {
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), fields)
webhook := &Webhook{}
if err != nil {
return webhook, err
@ -49,11 +51,11 @@ func (c *Client) UpdateWebhook(webhookID string, fields []WebhookField) (*Webhoo
// ListWebhooks - Lists webhooks for an app.
// Endpoint: GET /v1/notifications/webhooks
func (c *Client) ListWebhooks(anchorType string) (*ListWebhookResponse, error) {
func (c *Client) ListWebhooks(ctx context.Context, anchorType string) (*ListWebhookResponse, error) {
if len(anchorType) == 0 {
anchorType = AncorTypeApplication
}
req, err := c.NewRequest(http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), nil)
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), nil)
q := req.URL.Query()
q.Add("anchor_type", anchorType)
req.URL.RawQuery = q.Encode()
@ -68,8 +70,8 @@ func (c *Client) ListWebhooks(anchorType string) (*ListWebhookResponse, error) {
// DeleteWebhook - Deletes a webhook, by ID.
// Endpoint: DELETE /v1/notifications/webhooks/ID
func (c *Client) DeleteWebhook(webhookID string) error {
req, err := c.NewRequest(http.MethodDelete, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), nil)
func (c *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
req, err := c.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), nil)
if err != nil {
return err
}
@ -80,7 +82,7 @@ func (c *Client) DeleteWebhook(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(httpReq *http.Request, webhookID string) (*VerifyWebhookResponse, error) {
func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Request, webhookID string) (*VerifyWebhookResponse, error) {
type verifyWebhookSignatureRequest struct {
AuthAlgo string `json:"auth_algo,omitempty"`
CertURL string `json:"cert_url,omitempty"`
@ -88,16 +90,18 @@ func (c *Client) VerifyWebhookSignature(httpReq *http.Request, webhookID string)
TransmissionSig string `json:"transmission_sig,omitempty"`
TransmissionTime string `json:"transmission_time,omitempty"`
WebhookID string `json:"webhook_id,omitempty"`
WebhookEvent json.RawMessage `json:"webhook_event"`
Event json.RawMessage `json:"webhook_event,omitempty"`
}
// Read the content
var bodyBytes []byte
if httpReq.Body != nil {
bodyBytes, _ = ioutil.ReadAll(httpReq.Body)
bodyBytes, _ = io.ReadAll(httpReq.Body)
} else {
return nil, errors.New("cannot verify webhook for HTTP Request with empty body")
}
// Restore the io.ReadCloser to its original state
httpReq.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
httpReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
verifyRequest := verifyWebhookSignatureRequest{
AuthAlgo: httpReq.Header.Get("PAYPAL-AUTH-ALGO"),
@ -106,12 +110,12 @@ func (c *Client) VerifyWebhookSignature(httpReq *http.Request, webhookID string)
TransmissionSig: httpReq.Header.Get("PAYPAL-TRANSMISSION-SIG"),
TransmissionTime: httpReq.Header.Get("PAYPAL-TRANSMISSION-TIME"),
WebhookID: webhookID,
WebhookEvent: json.RawMessage(bodyBytes),
Event: json.RawMessage(bodyBytes),
}
response := &VerifyWebhookResponse{}
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/verify-webhook-signature"), verifyRequest)
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/verify-webhook-signature"), verifyRequest)
if err != nil {
return nil, err
}
@ -122,3 +126,19 @@ func (c *Client) VerifyWebhookSignature(httpReq *http.Request, webhookID string)
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,6 +1,7 @@
package paypal
import (
"context"
"fmt"
"net/http"
)
@ -10,9 +11,9 @@ import (
// Allows for the customisation of the payment experience
//
// Endpoint: POST /v1/payment-experience/web-profiles
func (c *Client) CreateWebProfile(wp WebProfile) (*WebProfile, error) {
func (c *Client) CreateWebProfile(ctx context.Context, wp WebProfile) (*WebProfile, error) {
url := fmt.Sprintf("%s%s", c.APIBase, "/v1/payment-experience/web-profiles")
req, err := c.NewRequest("POST", url, wp)
req, err := c.NewRequest(ctx, "POST", url, wp)
response := &WebProfile{}
if err != nil {
@ -29,11 +30,11 @@ func (c *Client) CreateWebProfile(wp WebProfile) (*WebProfile, error) {
// GetWebProfile gets an exists payment experience from Paypal
//
// Endpoint: GET /v1/payment-experience/web-profiles/<profile-id>
func (c *Client) GetWebProfile(profileID string) (*WebProfile, error) {
func (c *Client) GetWebProfile(ctx context.Context, profileID string) (*WebProfile, error) {
var wp WebProfile
url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", profileID)
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return &wp, err
@ -50,14 +51,14 @@ func (c *Client) GetWebProfile(profileID string) (*WebProfile, error) {
return &wp, nil
}
// GetWebProfiles retreieves web experience profiles from Paypal
// GetWebProfiles retrieves web experience profiles from Paypal
//
// Endpoint: GET /v1/payment-experience/web-profiles
func (c *Client) GetWebProfiles() ([]WebProfile, error) {
func (c *Client) GetWebProfiles(ctx context.Context) ([]WebProfile, error) {
var wps []WebProfile
url := fmt.Sprintf("%s%s", c.APIBase, "/v1/payment-experience/web-profiles")
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return wps, err
@ -73,7 +74,7 @@ func (c *Client) GetWebProfiles() ([]WebProfile, error) {
// SetWebProfile sets a web experience profile in Paypal with given id
//
// Endpoint: PUT /v1/payment-experience/web-profiles
func (c *Client) SetWebProfile(wp WebProfile) error {
func (c *Client) SetWebProfile(ctx context.Context, wp WebProfile) error {
if wp.ID == "" {
return fmt.Errorf("paypal: no ID specified for WebProfile")
@ -81,7 +82,7 @@ func (c *Client) SetWebProfile(wp WebProfile) error {
url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", wp.ID)
req, err := c.NewRequest("PUT", url, wp)
req, err := c.NewRequest(ctx, "PUT", url, wp)
if err != nil {
return err
@ -97,11 +98,11 @@ func (c *Client) SetWebProfile(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(profileID string) error {
func (c *Client) DeleteWebProfile(ctx context.Context, profileID string) error {
url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", profileID)
req, err := c.NewRequest("DELETE", url, nil)
req, err := c.NewRequest(ctx, "DELETE", url, nil)
if err != nil {
return err