forked from go-packages/paypal
Compare commits
195 Commits
fix-quanti
...
master
Author | SHA1 | Date | |
---|---|---|---|
5c4c0e1b7b | |||
63bbbdf1e1 | |||
283485068c | |||
fb37ee85f2 | |||
f840b7aac7 | |||
6217511bbd | |||
27ffa97190 | |||
|
0a5a40c12c | ||
|
c9c1a38641 | ||
|
9451befb2b | ||
|
daabe58f88 | ||
|
5052fd4286 | ||
|
3713084b64 | ||
|
bfdf8e6e97 | ||
|
6ebb0352bc | ||
|
e5db87f4a3 | ||
|
cd3344edd1 | ||
|
b8f2c8c573 | ||
|
5fddf59f71 | ||
|
26473c3630 | ||
|
ebf596676e | ||
|
4c16ffad0a | ||
|
1bb626d559 | ||
|
f0575ee562 | ||
|
defe3e09a4 | ||
|
efc9bbdee0 | ||
|
b7d3cfd093 | ||
|
d94b350a31 | ||
|
48f8ee15ce | ||
|
7dc1f997d6 | ||
|
c9ae5c4190 | ||
|
5c5393bd47 | ||
|
d381642422 | ||
|
ca6845e257 | ||
|
fc3ffe5b60 | ||
|
bf9103c70e | ||
|
bf0c207b52 | ||
|
28f2333770 | ||
|
d815f6d8d9 | ||
|
1a2c109908 | ||
|
50acd8fce1 | ||
|
64bfbc9b80 | ||
|
6fb262a2b0 | ||
|
7599a2d162 | ||
|
ef48df5471 | ||
|
5a5daa8596 | ||
|
cbabac29c8 | ||
|
e6a47fe5e7 | ||
|
59e3c9f3b0 | ||
|
326d69cc9e | ||
|
4ab23e5f78 | ||
|
06067823c6 | ||
|
d5cba40cd6 | ||
|
c2074af736 | ||
|
297b4fa2c6 | ||
|
8e5d5220ab | ||
|
2d088532de | ||
|
f195993596 | ||
|
d2210adf9c | ||
|
7f2eec9b56 | ||
|
392c0e9d69 | ||
|
a3977a8e74 | ||
|
021cc68201 | ||
|
bbd66c6089 | ||
|
41099f6b6e | ||
|
ed294ef317 | ||
|
d90cb75e47 | ||
|
62598e5880 | ||
|
52acc61786 | ||
|
1e23f8dd7d | ||
|
36281c1526 | ||
|
0a85944fee | ||
|
f694e414cd | ||
|
a3c2eaa0d4 | ||
|
a5cff3c18c | ||
|
df9918548b | ||
|
f807b7d046 | ||
|
3b835ea26a | ||
|
e83fd911e0 | ||
|
cc8b3cee69 | ||
|
0f9d1cca16 | ||
|
98cae62470 | ||
|
ef386ff32f | ||
|
476102bb76 | ||
|
13112c66e5 | ||
|
5508bfebaf | ||
|
ced90e676f | ||
|
abd51d2823 | ||
|
d1e5575c35 | ||
|
289b2669ef | ||
|
761c20a4ed | ||
|
18b47a0652 | ||
|
a0d03ecb0d | ||
|
905bf2eaf2 | ||
|
a678ccb4b1 | ||
|
e84c6d06c5 | ||
|
2e16e4fb10 | ||
|
0461b35d07 | ||
|
d355a65df0 | ||
|
b3eb2c69dd | ||
|
efe72c1ed4 | ||
|
fc9dcf749b | ||
|
4f66415fcd | ||
|
21b349dbdc | ||
|
497963d8a5 | ||
|
7827c1418f | ||
|
907cf40be1 | ||
|
1fb8e0b1fa | ||
|
2091d46d04 | ||
|
6f0fe4d870 | ||
|
8686a619a4 | ||
|
0e7098bda4 | ||
|
89088bee4e | ||
|
5822d5ee58 | ||
|
16c52d39ba | ||
|
5feda2cf5e | ||
|
a6e6c7ae98 | ||
|
bffc96851f | ||
|
c865af7932 | ||
|
c4226ce43c | ||
|
06a298ef76 | ||
|
cd72fd3aec | ||
|
8e3811377b | ||
|
fee7108fae | ||
|
c0ae3c0d06 | ||
|
32920d672b | ||
|
dc6329e927 | ||
|
b200e0a324 | ||
|
55f369d8c8 | ||
|
e70e544c1a | ||
|
f7f8c60772 | ||
|
035df40512 | ||
|
6f5da20f7c | ||
|
1ac6304458 | ||
|
167f2d58ab | ||
|
fb6ff6be3d | ||
|
61f8ec387c | ||
|
458e08c529 | ||
|
188f7c26fc | ||
|
ce57fa2d8b | ||
|
cce5baf4a0 | ||
|
37b06bc520 | ||
|
04ab7f83a0 | ||
|
224bd1f949 | ||
|
637e1c4b15 | ||
|
aeec25d551 | ||
|
fd64790cd2 | ||
|
0862ea5ecc | ||
|
1daa788741 | ||
|
586d690e8e | ||
|
ed0c8f85a0 | ||
|
80ccf89d37 | ||
|
6891f8d2f0 | ||
|
3f993bc542 | ||
|
da794934b8 | ||
|
265abe72b2 | ||
|
888360be1f | ||
|
e6cc18e840 | ||
|
a329399695 | ||
|
da7d8507ff | ||
|
42de9cae2e | ||
|
f51b1fe7a0 | ||
|
dd82be9889 | ||
|
973f502217 | ||
|
c85effd4a6 | ||
|
fd873cc35b | ||
|
ebc7df5708 | ||
|
cc05027dd1 | ||
|
2c6b470ed7 | ||
|
17ba889ca0 | ||
|
561690178b | ||
|
4557d3e5a7 | ||
|
3adfc8c315 | ||
|
1f766901f9 | ||
|
0c3090ecb8 | ||
|
4d9251997c | ||
|
5af7837061 | ||
|
114f3ab4be | ||
|
6f2ea3a580 | ||
|
4303b79440 | ||
|
560c513235 | ||
|
bb976e776e | ||
|
7a8d9e5531 | ||
|
c23e130afd | ||
|
3896aaa167 | ||
|
e31da31dcc | ||
|
9c283e9d42 | ||
|
f17191367d | ||
|
2eacad2d5d | ||
|
d5a45e7e9a | ||
|
36f64e9a3b | ||
|
f04460162e | ||
|
f7201fdd87 | ||
|
2148226300 | ||
|
caff1125ce |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
50
.github/workflows/lint-test.yaml
vendored
Normal 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 ./...
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
.vscode
|
||||
vendor/
|
12
.travis.yml
12
.travis.yml
|
@ -1,12 +0,0 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- 1.10
|
||||
install:
|
||||
- export PATH=$PATH:$HOME/gopath/bin
|
||||
script:
|
||||
- go test -v -race
|
10
CONTRIBUTING.md
Normal file
10
CONTRIBUTING.md
Normal 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.
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 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
|
||||
|
|
279
README.md
279
README.md
|
@ -1,130 +1,31 @@
|
|||
[![Go Report Card](https://goreportcard.com/badge/logpacker/PayPal-Go-SDK)](https://goreportcard.com/report/logpacker/PayPal-Go-SDK)
|
||||
[![Build Status](https://travis-ci.org/logpacker/PayPal-Go-SDK.svg?branch=master)](https://travis-ci.org/logpacker/PayPal-Go-SDK)
|
||||
[![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/logpacker/PayPal-Go-SDK)
|
||||
[![Chat at https://gitter.im/logpacker/PayPal-Go-SDK](https://img.shields.io/badge/gitter-dev_chat-46bc99.svg)](https://gitter.im/logpacker/PayPal-Go-SDK?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![Sourcegraph](https://sourcegraph.com/github.com/logpacker/PayPal-Go-SDK/-/badge.svg)](https://sourcegraph.com/github.com/logpacker/PayPal-Go-SDK?badge)
|
||||
[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>
|
||||
|
||||
### Coverage
|
||||
* POST /v1/oauth2/token
|
||||
* POST /v1/payments/payment
|
||||
* GET /v1/payments/payment/**ID**
|
||||
* GET /v1/payments/payment
|
||||
* GET /v1/payments/authorization/**ID**
|
||||
* POST /v1/payments/authorization/**ID**/capture
|
||||
* POST /v1/payments/authorization/**ID**/void
|
||||
* POST /v1/payments/authorization/**ID**/reauthorize
|
||||
* GET /v1/payments/sale/**ID**
|
||||
* POST /v1/payments/sale/**ID**/refund
|
||||
* GET /v1/payments/refund/**ID**
|
||||
* GET /v1/payments/orders/**ID**
|
||||
* POST /v1/payments/orders/**ID**/authorize
|
||||
* POST /v1/payments/orders/**ID**/capture
|
||||
* POST /v1/payments/orders/**ID**/do-void
|
||||
* POST /v1/identity/openidconnect/tokenservice
|
||||
* GET /v1/identity/openidconnect/userinfo/?schema=**SCHEMA**
|
||||
* POST /v1/payments/payouts
|
||||
* GET /v1/payments/payouts/**ID**
|
||||
* GET /v1/payments/payouts-item/**ID**
|
||||
* POST /v1/payments/payouts-item/**ID**/cancel
|
||||
* GET /v1/payment-experience/web-profiles
|
||||
* POST /v1/payment-experience/web-profiles
|
||||
* GET /v1/payment-experience/web-profiles/**ID**
|
||||
* PUT /v1/payment-experience/web-profiles/**ID**
|
||||
* DELETE /v1/payment-experience/web-profiles/**ID**
|
||||
* POST /v1/vault/credit-cards
|
||||
* DELETE /v1/vault/credit-cards/**ID**
|
||||
* PATCH /v1/vault/credit-cards/**ID**
|
||||
* GET /v1/vault/credit-cards/**ID**
|
||||
* GET /v1/vault/credit-cards
|
||||
* POST /v1/payments/billing-plans
|
||||
* PATCH /v1/payments/billing-plans/***ID***
|
||||
* POST /v1/payments/billing-agreements
|
||||
* POST /v1/payments/billing-agreements/***TOKEN***/agreement-execute
|
||||
# Go client for PayPal REST API
|
||||
|
||||
### Missing endpoints
|
||||
It is possible that some endpoints are missing in this SDK Client, but you can use built-in **paypalsdk** functions to perform a request: **NewClient -> NewRequest -> SendWithAuth**
|
||||
## Paypal REST API Docs
|
||||
|
||||
### New Client
|
||||
[Get started with PayPal REST APIs](https://developer.paypal.com/api/rest/)
|
||||
|
||||
## Missing endpoints
|
||||
|
||||
It is possible that some endpoints are missing in this client, but you can use built-in `paypal` functions to perform a request: `NewClient -> NewRequest -> SendWithAuth`
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import "github.com/logpacker/PayPal-Go-SDK"
|
||||
import "github.com/plutov/paypal/v4"
|
||||
|
||||
// Create a client instance
|
||||
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
|
||||
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox)
|
||||
c.SetLog(os.Stdout) // Set log to terminal stdout
|
||||
|
||||
accessToken, err := c.GetAccessToken()
|
||||
```
|
||||
|
||||
### Create direct paypal payment
|
||||
|
||||
```go
|
||||
amount := paypalsdk.Amount{
|
||||
Total: "7.00",
|
||||
Currency: "USD",
|
||||
}
|
||||
redirectURI := "http://example.com/redirect-uri"
|
||||
cancelURI := "http://example.com/cancel-uri"
|
||||
description := "Description for this payment"
|
||||
paymentResult, err := c.CreateDirectPaypalPayment(amount, redirectURI, cancelURI, description)
|
||||
```
|
||||
|
||||
### Create custom payment
|
||||
```go
|
||||
p := paypalsdk.Payment{
|
||||
Intent: "sale",
|
||||
Payer: &paypalsdk.Payer{
|
||||
PaymentMethod: "credit_card",
|
||||
FundingInstruments: []paypalsdk.FundingInstrument{paypalsdk.FundingInstrument{
|
||||
CreditCard: &paypalsdk.CreditCard{
|
||||
Number: "4111111111111111",
|
||||
Type: "visa",
|
||||
ExpireMonth: "11",
|
||||
ExpireYear: "2020",
|
||||
CVV2: "777",
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
},
|
||||
}},
|
||||
},
|
||||
Transactions: []paypalsdk.Transaction{paypalsdk.Transaction{
|
||||
Amount: &paypalsdk.Amount{
|
||||
Currency: "USD",
|
||||
Total: "7.00",
|
||||
},
|
||||
Description: "My Payment",
|
||||
}},
|
||||
RedirectURLs: &paypalsdk.RedirectURLs{
|
||||
ReturnURL: "http://...",
|
||||
CancelURL: "http://...",
|
||||
},
|
||||
}
|
||||
paymentResponse, err := client.CreatePayment(p)
|
||||
```
|
||||
|
||||
### Execute approved payment
|
||||
|
||||
```go
|
||||
paymentID := "PAY-17S8410768582940NKEE66EQ"
|
||||
payerID := "7E7MGXCWTTKK2"
|
||||
executeResult, err := c.ExecuteApprovedPayment(paymentID, payerID)
|
||||
```
|
||||
|
||||
### Get payment by ID
|
||||
|
||||
```go
|
||||
payment, err := c.GetPayment("PAY-17S8410768582940NKEE66EQ")
|
||||
```
|
||||
|
||||
### Get list of payments
|
||||
|
||||
```go
|
||||
payments, err := c.GetPayments()
|
||||
```
|
||||
|
||||
### Get authorization by ID
|
||||
## Get authorization by ID
|
||||
|
||||
```go
|
||||
auth, err := c.GetAuthorization("2DC87612EK520411B")
|
||||
|
@ -133,7 +34,7 @@ auth, err := c.GetAuthorization("2DC87612EK520411B")
|
|||
### Capture authorization
|
||||
|
||||
```go
|
||||
capture, err := c.CaptureAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true)
|
||||
capture, err := c.CaptureAuthorization(authID, &paypal.Amount{Total: "7.00", Currency: "USD"}, true)
|
||||
```
|
||||
|
||||
### Void authorization
|
||||
|
@ -145,22 +46,7 @@ auth, err := c.VoidAuthorization(authID)
|
|||
### Reauthorize authorization
|
||||
|
||||
```go
|
||||
auth, err := c.ReauthorizeAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
|
||||
```
|
||||
|
||||
### Get Sale by ID
|
||||
|
||||
```go
|
||||
sale, err := c.GetSale("36C38912MN9658832")
|
||||
```
|
||||
|
||||
### Refund Sale by ID
|
||||
|
||||
```go
|
||||
// Full
|
||||
refund, err := c.RefundSale(saleID, nil)
|
||||
// Partial
|
||||
refund, err := c.RefundSale(saleID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
|
||||
auth, err := c.ReauthorizeAuthorization(authID, &paypal.Amount{Total: "7.00", Currency: "USD"})
|
||||
```
|
||||
|
||||
### Get Refund by ID
|
||||
|
@ -175,22 +61,32 @@ refund, err := c.GetRefund("O-4J082351X3132253H")
|
|||
order, err := c.GetOrder("O-4J082351X3132253H")
|
||||
```
|
||||
|
||||
### Create an Order
|
||||
|
||||
```go
|
||||
ctx := context.Background()
|
||||
units := []paypal.PurchaseUnitRequest{}
|
||||
source := &paypal.PaymentSource{}
|
||||
appCtx := &paypal.ApplicationContext{}
|
||||
order, err := c.CreateOrder(ctx, paypal.OrderIntentCapture, units, ource, appCtx)
|
||||
```
|
||||
|
||||
### Update Order by ID
|
||||
|
||||
```go
|
||||
order, err := c.UpdateOrder("O-4J082351X3132253H", []paypal.PurchaseUnitRequest{})
|
||||
```
|
||||
|
||||
### Authorize Order
|
||||
|
||||
```go
|
||||
auth, err := c.AuthorizeOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
|
||||
auth, err := c.AuthorizeOrder(orderID, paypal.AuthorizeOrderRequest{})
|
||||
```
|
||||
|
||||
### Capture Order
|
||||
|
||||
```go
|
||||
capture, err := c.CaptureOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true, nil)
|
||||
```
|
||||
|
||||
### Void Order
|
||||
|
||||
```go
|
||||
order, err := c.VoidOrder(orderID)
|
||||
capture, err := c.CaptureOrder(orderID, paypal.CaptureOrderRequest{})
|
||||
```
|
||||
|
||||
### Identity
|
||||
|
@ -210,15 +106,15 @@ userInfo, err := c.GetUserInfo("openid")
|
|||
### Create single payout to email
|
||||
|
||||
```go
|
||||
payout := paypalsdk.Payout{
|
||||
SenderBatchHeader: &paypalsdk.SenderBatchHeader{
|
||||
payout := paypal.Payout{
|
||||
SenderBatchHeader: &paypal.SenderBatchHeader{
|
||||
EmailSubject: "Subject will be displayed on PayPal",
|
||||
},
|
||||
Items: []paypalsdk.PayoutItem{
|
||||
paypalsdk.PayoutItem{
|
||||
Items: []paypal.PayoutItem{
|
||||
paypal.PayoutItem{
|
||||
RecipientType: "EMAIL",
|
||||
Receiver: "single-email-payout@mail.com",
|
||||
Amount: &paypalsdk.AmountPayout{
|
||||
Amount: &paypal.AmountPayout{
|
||||
Value: "15.11",
|
||||
Currency: "USD",
|
||||
},
|
||||
|
@ -228,7 +124,7 @@ payout := paypalsdk.Payout{
|
|||
},
|
||||
}
|
||||
|
||||
payoutResp, err := c.CreateSinglePayout(payout)
|
||||
payoutResp, err := c.CreatePayout(payout)
|
||||
```
|
||||
|
||||
### Get payout by ID
|
||||
|
@ -290,7 +186,6 @@ webprofiles, err := c.GetWebProfiles()
|
|||
### Update web experience profile
|
||||
|
||||
```go
|
||||
|
||||
webprofile := WebProfile{
|
||||
ID: "XP-CP6S-W9DY-96H8-MVN2",
|
||||
Name: "Shop YeowZa! YeowZa! ",
|
||||
|
@ -308,7 +203,7 @@ err := c.DeleteWebProfile("XP-CP6S-W9DY-96H8-MVN2")
|
|||
|
||||
```go
|
||||
// Store CC
|
||||
c.StoreCreditCard(paypalsdk.CreditCard{
|
||||
c.StoreCreditCard(paypal.CreditCard{
|
||||
Number: "4417119669820331",
|
||||
Type: "visa",
|
||||
ExpireMonth: "11",
|
||||
|
@ -322,8 +217,8 @@ c.StoreCreditCard(paypalsdk.CreditCard{
|
|||
c.DeleteCreditCard("CARD-ID-123")
|
||||
|
||||
// Edit it
|
||||
c.PatchCreditCard("CARD-ID-123", []paypalsdk.CreditCardField{
|
||||
paypalsdk.CreditCardField{
|
||||
c.PatchCreditCard("CARD-ID-123", []paypal.CreditCardField{
|
||||
paypal.CreditCardField{
|
||||
Operation: "replace",
|
||||
Path: "/billing_address/line1",
|
||||
Value: "New value",
|
||||
|
@ -337,14 +232,80 @@ c.GetCreditCard("CARD-ID-123")
|
|||
c.GetCreditCards(nil)
|
||||
```
|
||||
|
||||
### How to Contribute
|
||||
### Webhooks
|
||||
|
||||
* Fork a repository
|
||||
* Add/Fix something
|
||||
* Check that tests are passing
|
||||
* Create PR
|
||||
```go
|
||||
// Create a webhook
|
||||
c.CreateWebhook(paypal.CreateWebhookRequest{
|
||||
URL: "webhook URL",
|
||||
EventTypes: []paypal.WebhookEventType{
|
||||
paypal.WebhookEventType{
|
||||
Name: "PAYMENT.AUTHORIZATION.CREATED",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
### Tests
|
||||
// Update a registered webhook
|
||||
c.UpdateWebhook("WebhookID", []paypal.WebhookField{
|
||||
paypal.WebhookField{
|
||||
Operation: "replace",
|
||||
Path: "/event_types",
|
||||
Value: []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "PAYMENT.SALE.REFUNDED",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
* Unit tests: `go test`
|
||||
* Integration tests: `go test -tags=integration`
|
||||
// Get a registered webhook
|
||||
c.GetWebhook("WebhookID")
|
||||
|
||||
// Delete a webhook
|
||||
c.DeleteWebhook("WebhookID")
|
||||
|
||||
// List registered webhooks
|
||||
c.ListWebhooks(paypal.AncorTypeApplication)
|
||||
```
|
||||
|
||||
### Generate Next Invoice Number
|
||||
|
||||
```go
|
||||
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
|
||||
c.GenerateInvoiceNumber(ctx) // might return something like "0001" or "0010".
|
||||
```
|
||||
|
||||
### Get Invoice Details by ID
|
||||
|
||||
```go
|
||||
// the second argument is an ID, it should be valid
|
||||
invoice, err := c.GetInvoiceDetails(ctx, "INV2-XFXV-YW42-ZANU-4F33")
|
||||
```
|
||||
|
||||
- for now, we are yet to implement the ShowAllInvoices endpoint, so use the following cURL request for the same(this gives you the list of invoice-IDs for this customer)
|
||||
|
||||
```bash
|
||||
curl -v -X GET https://api-m.sandbox.paypal.com/v2/invoicing/invoices?total_required=true \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <Token>"
|
||||
```
|
||||
|
||||
- refer to the beginning of this Usage section for obtaining a Token.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
- Fork a repository
|
||||
- Add/Fix something
|
||||
- Check that tests are passing
|
||||
- Create PR
|
||||
|
||||
Main contributors:
|
||||
|
||||
- [Alex Pliutau](https://github.com/plutov)
|
||||
- [Roopak Venkatakrishnan](https://github.com/roopakv)
|
||||
|
||||
## Tests
|
||||
|
||||
```
|
||||
go test -v ./...
|
||||
```
|
||||
|
|
|
@ -1,68 +1,84 @@
|
|||
package paypalsdk
|
||||
package paypal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// GetAuthorization returns an authorization by ID
|
||||
// Endpoint: GET /v1/payments/authorization/ID
|
||||
func (c *Client) GetAuthorization(authID string) (*Authorization, error) {
|
||||
// Endpoint: GET /v2/payments/authorizations/ID
|
||||
func (c *Client) GetAuthorization(ctx context.Context, authID string) (*Authorization, error) {
|
||||
buf := bytes.NewBuffer([]byte(""))
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payments/authorization/", authID), buf)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/payments/authorizations/", authID), buf)
|
||||
auth := &Authorization{}
|
||||
|
||||
if err != nil {
|
||||
return &Authorization{}, err
|
||||
return auth, err
|
||||
}
|
||||
|
||||
auth := &Authorization{}
|
||||
err = c.SendWithAuth(req, auth)
|
||||
return auth, err
|
||||
}
|
||||
|
||||
// CaptureAuthorization captures and process an existing authorization.
|
||||
// To use this method, the original payment must have Intent set to "authorize"
|
||||
// Endpoint: POST /v1/payments/authorization/ID/capture
|
||||
func (c *Client) CaptureAuthorization(authID string, a *Amount, isFinalCapture bool) (*Capture, error) {
|
||||
isFinalStr := strconv.FormatBool(isFinalCapture)
|
||||
// Endpoint: POST /v2/payments/authorizations/ID/capture
|
||||
func (c *Client) CaptureAuthorization(ctx context.Context, authID string, paymentCaptureRequest *PaymentCaptureRequest) (*PaymentCaptureResponse, error) {
|
||||
return c.CaptureAuthorizationWithPaypalRequestId(ctx, authID, paymentCaptureRequest, "")
|
||||
}
|
||||
|
||||
// CaptureAuthorization captures and process an existing authorization with idempotency.
|
||||
// To use this method, the original payment must have Intent set to "authorize"
|
||||
// Endpoint: POST /v2/payments/authorizations/ID/capture
|
||||
func (c *Client) CaptureAuthorizationWithPaypalRequestId(ctx context.Context,
|
||||
authID string,
|
||||
paymentCaptureRequest *PaymentCaptureRequest,
|
||||
requestID string,
|
||||
) (*PaymentCaptureResponse, error) {
|
||||
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/capture"), paymentCaptureRequest)
|
||||
paymentCaptureResponse := &PaymentCaptureResponse{}
|
||||
|
||||
buf := bytes.NewBuffer([]byte("{\"amount\":{\"currency\":\"" + a.Currency + "\",\"total\":\"" + a.Total + "\"},\"is_final_capture\":" + isFinalStr + "}"))
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/capture"), buf)
|
||||
if err != nil {
|
||||
return &Capture{}, err
|
||||
return paymentCaptureResponse, err
|
||||
}
|
||||
|
||||
capture := &Capture{}
|
||||
err = c.SendWithAuth(req, capture)
|
||||
return capture, err
|
||||
if requestID != "" {
|
||||
req.Header.Set("PayPal-Request-Id", requestID)
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, paymentCaptureResponse)
|
||||
return paymentCaptureResponse, err
|
||||
}
|
||||
|
||||
// VoidAuthorization voids a previously authorized payment
|
||||
// Endpoint: POST /v1/payments/authorization/ID/void
|
||||
func (c *Client) VoidAuthorization(authID string) (*Authorization, error) {
|
||||
// Endpoint: POST /v2/payments/authorizations/ID/void
|
||||
func (c *Client) VoidAuthorization(ctx context.Context, authID string) (*Authorization, error) {
|
||||
buf := bytes.NewBuffer([]byte(""))
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/void"), buf)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/void"), buf)
|
||||
auth := &Authorization{}
|
||||
|
||||
if err != nil {
|
||||
return &Authorization{}, err
|
||||
return auth, err
|
||||
}
|
||||
|
||||
auth := &Authorization{}
|
||||
err = c.SendWithAuth(req, auth)
|
||||
return auth, err
|
||||
}
|
||||
|
||||
// ReauthorizeAuthorization reauthorize a Paypal account payment.
|
||||
// PayPal recommends to reauthorize payment after ~3 days
|
||||
// Endpoint: POST /v1/payments/authorization/ID/reauthorize
|
||||
func (c *Client) ReauthorizeAuthorization(authID string, a *Amount) (*Authorization, error) {
|
||||
buf := bytes.NewBuffer([]byte("{\"amount\":{\"currency\":\"" + a.Currency + "\",\"total\":\"" + a.Total + "\"}}"))
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/authorization/"+authID+"/reauthorize"), buf)
|
||||
// PayPal recommends reauthorizing payment after ~3 days
|
||||
// Endpoint: POST /v2/payments/authorizations/ID/reauthorize
|
||||
func (c *Client) ReauthorizeAuthorization(ctx context.Context, authID string, a *Amount) (*Authorization, error) {
|
||||
buf := bytes.NewBuffer([]byte(`{"amount":{"currency_code":"` + a.Currency + `","value":"` + a.Total + `"}}`))
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/authorizations/"+authID+"/reauthorize"), buf)
|
||||
auth := &Authorization{}
|
||||
|
||||
if err != nil {
|
||||
return &Authorization{}, err
|
||||
return auth, err
|
||||
}
|
||||
|
||||
auth := &Authorization{}
|
||||
err = c.SendWithAuth(req, auth)
|
||||
return auth, err
|
||||
}
|
||||
|
|
98
billing.go
98
billing.go
|
@ -1,98 +0,0 @@
|
|||
package paypalsdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// CreateBillingResp struct
|
||||
CreateBillingResp struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
PaymentDefinitions []PaymentDefinition `json:"payment_definitions,omitempty"`
|
||||
MerchantPreferences MerchantPreferences `json:"merchant_preferences,omitempty"`
|
||||
CreateTime time.Time `json:"create_time,omitempty"`
|
||||
UpdateTime time.Time `json:"update_time,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// CreateAgreementResp struct
|
||||
CreateAgreementResp struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Plan BillingPlan `json:"plan,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
StartTime time.Time `json:"start_time,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
// CreateBillingPlan creates a billing plan in Paypal
|
||||
// Endpoint: POST /v1/payments/billing-plans
|
||||
func (c *Client) CreateBillingPlan(plan BillingPlan) (*CreateBillingResp, error) {
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), plan)
|
||||
response := &CreateBillingResp{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = c.SendWithAuth(req, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// ActivatePlan activates a billing plan
|
||||
// By default, a new plan is not activated
|
||||
// Endpoint: PATCH /v1/payments/billing-plans/
|
||||
func (c *Client) ActivatePlan(planID string) error {
|
||||
buf := bytes.NewBuffer([]byte("[{\"op\":\"replace\",\"path\":\"/\",\"value\":{\"state\":\"ACTIVE\"}}]"))
|
||||
req, err := http.NewRequest("PATCH", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans/"+planID), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.SetBasicAuth(c.ClientID, c.Secret)
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
|
||||
return c.SendWithAuth(req, nil)
|
||||
}
|
||||
|
||||
// CreateBillingAgreement creates an agreement for specified plan
|
||||
// Endpoint: POST /v1/payments/billing-agreements
|
||||
func (c *Client) CreateBillingAgreement(a BillingAgreement) (*CreateAgreementResp, error) {
|
||||
// PayPal needs only ID, so we will remove all fields except Plan ID
|
||||
a.Plan = BillingPlan{
|
||||
ID: a.Plan.ID,
|
||||
}
|
||||
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements"), a)
|
||||
response := &CreateAgreementResp{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = c.SendWithAuth(req, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// ExecuteApprovedAgreement - Use this call to execute (complete) a PayPal agreement that has been approved by the payer.
|
||||
// Endpoint: POST /v1/payments/billing-agreements/token/agreement-execute
|
||||
func (c *Client) ExecuteApprovedAgreement(token string) (*ExecuteAgreementResponse, error) {
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements/"+token+"/agreement-execute"), nil)
|
||||
if err != nil {
|
||||
return &ExecuteAgreementResponse{}, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(c.ClientID, c.Secret)
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
|
||||
|
||||
e := ExecuteAgreementResponse{}
|
||||
|
||||
if err = c.SendWithAuth(req, &e); err != nil {
|
||||
return &e, err
|
||||
}
|
||||
|
||||
if e.ID == "" {
|
||||
return &e, errors.New("Unable to execute agreement with token=" + token)
|
||||
}
|
||||
|
||||
return &e, err
|
||||
}
|
108
billing_test.go
108
billing_test.go
|
@ -1,108 +0,0 @@
|
|||
package paypalsdk_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
pp "github.com/logpacker/PayPal-Go-SDK"
|
||||
)
|
||||
|
||||
func BillingExample() {
|
||||
plan := pp.BillingPlan{
|
||||
Name: "Plan with Regular and Trial Payment Definitions",
|
||||
Description: "Plan with regular and trial payment definitions.",
|
||||
Type: "fixed",
|
||||
PaymentDefinitions: []pp.PaymentDefinition{
|
||||
pp.PaymentDefinition{
|
||||
Name: "Regular payment definition",
|
||||
Type: "REGULAR",
|
||||
Frequency: "MONTH",
|
||||
FrequencyInterval: "2",
|
||||
Amount: pp.AmountPayout{
|
||||
Value: "100",
|
||||
Currency: "USD",
|
||||
},
|
||||
Cycles: "12",
|
||||
ChargeModels: []pp.ChargeModel{
|
||||
pp.ChargeModel{
|
||||
Type: "SHIPPING",
|
||||
Amount: pp.AmountPayout{
|
||||
Value: "10",
|
||||
Currency: "USD",
|
||||
},
|
||||
},
|
||||
pp.ChargeModel{
|
||||
Type: "TAX",
|
||||
Amount: pp.AmountPayout{
|
||||
Value: "12",
|
||||
Currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pp.PaymentDefinition{
|
||||
Name: "Trial payment definition",
|
||||
Type: "trial",
|
||||
Frequency: "week",
|
||||
FrequencyInterval: "5",
|
||||
Amount: pp.AmountPayout{
|
||||
Value: "9.19",
|
||||
Currency: "USD",
|
||||
},
|
||||
Cycles: "2",
|
||||
ChargeModels: []pp.ChargeModel{
|
||||
pp.ChargeModel{
|
||||
Type: "SHIPPING",
|
||||
Amount: pp.AmountPayout{
|
||||
Value: "1",
|
||||
Currency: "USD",
|
||||
},
|
||||
},
|
||||
pp.ChargeModel{
|
||||
Type: "TAX",
|
||||
Amount: pp.AmountPayout{
|
||||
Value: "2",
|
||||
Currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MerchantPreferences: &pp.MerchantPreferences{
|
||||
SetupFee: &pp.AmountPayout{
|
||||
Value: "1",
|
||||
Currency: "USD",
|
||||
},
|
||||
ReturnURL: "http://www.paypal.com",
|
||||
CancelURL: "http://www.paypal.com/cancel",
|
||||
AutoBillAmount: "YES",
|
||||
InitialFailAmountAction: "CONTINUE",
|
||||
MaxFailAttempts: "0",
|
||||
},
|
||||
}
|
||||
c, err := pp.NewClient("clientID", "secretID", pp.APIBaseSandBox)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = c.GetAccessToken()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
planResp, err := c.CreateBillingPlan(plan)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = c.ActivatePlan(planResp.ID)
|
||||
fmt.Println(err)
|
||||
agreement := pp.BillingAgreement{
|
||||
Name: "Fast Speed Agreement",
|
||||
Description: "Agreement for Fast Speed Plan",
|
||||
StartDate: pp.JSONTime(time.Now().Add(time.Hour * 24)),
|
||||
Plan: pp.BillingPlan{ID: planResp.ID},
|
||||
Payer: pp.Payer{
|
||||
PaymentMethod: "paypal",
|
||||
},
|
||||
}
|
||||
resp, err := c.CreateBillingAgreement(agreement)
|
||||
fmt.Println(err, resp)
|
||||
}
|
113
client.go
113
client.go
|
@ -1,19 +1,19 @@
|
|||
package paypalsdk
|
||||
package paypal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewClient returns new Client struct
|
||||
// APIBase is a base API URL, for testing you can use paypalsdk.APIBaseSandBox
|
||||
// APIBase is a base API URL, for testing you can use paypal.APIBaseSandBox
|
||||
func NewClient(clientID string, secret string, APIBase string) (*Client, error) {
|
||||
if clientID == "" || secret == "" || APIBase == "" {
|
||||
return nil, errors.New("ClientID, Secret and APIBase are required to create a Client")
|
||||
|
@ -30,25 +30,25 @@ func NewClient(clientID string, secret string, APIBase string) (*Client, error)
|
|||
// GetAccessToken returns struct of TokenResponse
|
||||
// No need to call SetAccessToken to apply new access token for current Client
|
||||
// Endpoint: POST /v1/oauth2/token
|
||||
func (c *Client) GetAccessToken() (*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
|
||||
}
|
||||
|
||||
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
|
||||
|
||||
t := TokenResponse{}
|
||||
err = c.SendWithBasicAuth(req, &t)
|
||||
response := &TokenResponse{}
|
||||
err = c.SendWithBasicAuth(req, response)
|
||||
|
||||
// Set Token fur current Client
|
||||
if t.Token != "" {
|
||||
c.Token = &t
|
||||
c.tokenExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second)
|
||||
if response.Token != "" {
|
||||
c.Token = response
|
||||
c.tokenExpiresAt = time.Now().Add(time.Duration(response.ExpiresIn) * time.Second)
|
||||
}
|
||||
|
||||
return &t, err
|
||||
return response, err
|
||||
}
|
||||
|
||||
// SetHTTPClient sets *http.Client to current client
|
||||
|
@ -65,13 +65,19 @@ func (c *Client) SetAccessToken(token string) {
|
|||
}
|
||||
|
||||
// SetLog will set/change the output destination.
|
||||
// If log file is set paypalsdk will log all requests and responses to this Writer
|
||||
// If log file is set paypal will log all requests and responses to this Writer
|
||||
func (c *Client) SetLog(log io.Writer) {
|
||||
c.Log = log
|
||||
}
|
||||
|
||||
// SetReturnRepresentation enables verbose response
|
||||
// Verbose response: https://developer.paypal.com/docs/api/orders/v2/#orders-authorize-header-parameters
|
||||
func (c *Client) SetReturnRepresentation() {
|
||||
c.returnRepresentation = true
|
||||
}
|
||||
|
||||
// Send makes a request to the API, the response body will be
|
||||
// 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 (
|
||||
|
@ -88,33 +94,50 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
|
|||
if req.Header.Get("Content-type") == "" {
|
||||
req.Header.Set("Content-type", "application/json")
|
||||
}
|
||||
if c.returnRepresentation {
|
||||
req.Header.Set("Prefer", "return=representation")
|
||||
}
|
||||
if c.Log != nil {
|
||||
if reqDump, err := httputil.DumpRequestOut(req, true); err == nil {
|
||||
c.Log.Write([]byte(fmt.Sprintf("Request: %s\n", reqDump)))
|
||||
}
|
||||
}
|
||||
|
||||
resp, err = c.Client.Do(req)
|
||||
c.log(req, resp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -125,17 +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 {
|
||||
if c.Token != nil {
|
||||
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
|
||||
// c.Token will be updated in GetAccessToken call
|
||||
if _, err := c.GetAccessToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
|
||||
if c.Token == nil || (!c.tokenExpiresAt.IsZero() && time.Until(c.tokenExpiresAt) < RequestNewTokenBeforeExpiresIn) {
|
||||
// c.Token will be updated in GetAccessToken call
|
||||
if _, err := c.GetAccessToken(req.Context()); err != nil {
|
||||
// c.Unlock()
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
|
||||
// Unlock the client mutex before sending the request, this allows multiple requests
|
||||
// to be in progress at the same time.
|
||||
// c.Unlock()
|
||||
c.mu.Unlock()
|
||||
return c.Send(req, v)
|
||||
}
|
||||
|
||||
|
@ -148,34 +179,14 @@ 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 {
|
||||
var b []byte
|
||||
b, err := json.Marshal(&payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
94
client_test.go
Normal 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
118
const.go
Normal 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"
|
||||
)
|
10
doc.go
10
doc.go
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
Package paypalsdk provides a wrapper to PayPal API (https://developer.paypal.com/webapps/developer/docs/api/).
|
||||
The first thing you do is to create a Client (you can select API base URL using paypalsdk contants).
|
||||
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
|
||||
Package paypal provides a wrapper to PayPal API (https://developer.paypal.com/webapps/developer/docs/api/).
|
||||
The first thing you do is to create a Client (you can select API base URL using paypal contants).
|
||||
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox)
|
||||
Then you can get an access token from PayPal:
|
||||
accessToken, err := c.GetAccessToken()
|
||||
After you have an access token you can call built-in functions to get data from PayPal.
|
||||
paypalsdk will assign all responses to go structures.
|
||||
paypal will assign all responses to go structures.
|
||||
*/
|
||||
package paypalsdk
|
||||
package paypal
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
package paypalsdk_test
|
||||
|
||||
import paypalsdk "github.com/logpacker/PayPal-Go-SDK"
|
||||
|
||||
func Example() {
|
||||
// Initialize client
|
||||
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Retrieve access token
|
||||
_, err = c.GetAccessToken()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create credit card payment
|
||||
p := paypalsdk.Payment{
|
||||
Intent: "sale",
|
||||
Payer: &paypalsdk.Payer{
|
||||
PaymentMethod: "credit_card",
|
||||
FundingInstruments: []paypalsdk.FundingInstrument{{
|
||||
CreditCard: &paypalsdk.CreditCard{
|
||||
Number: "4111111111111111",
|
||||
Type: "visa",
|
||||
ExpireMonth: "11",
|
||||
ExpireYear: "2020",
|
||||
CVV2: "777",
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
},
|
||||
}},
|
||||
},
|
||||
Transactions: []paypalsdk.Transaction{{
|
||||
Amount: &paypalsdk.Amount{
|
||||
Currency: "USD",
|
||||
Total: "7.00",
|
||||
},
|
||||
Description: "My Payment",
|
||||
}},
|
||||
RedirectURLs: &paypalsdk.RedirectURLs{
|
||||
ReturnURL: "http://...",
|
||||
CancelURL: "http://...",
|
||||
},
|
||||
}
|
||||
_, err = c.CreatePayment(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
61
filter.go
Normal file
61
filter.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package paypal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const format = "2006-01-02T15:04:05Z"
|
||||
|
||||
// Filter type
|
||||
type Filter struct {
|
||||
fields []fmt.Stringer
|
||||
}
|
||||
|
||||
func (s *Filter) String() string {
|
||||
var filter string
|
||||
for i, f := range s.fields {
|
||||
if i == 0 {
|
||||
filter = "?" + f.String()
|
||||
} else {
|
||||
filter = filter + "&" + f.String()
|
||||
}
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
// TextField type
|
||||
type TextField struct {
|
||||
name string
|
||||
Is string
|
||||
}
|
||||
|
||||
func (d TextField) String() string {
|
||||
return fmt.Sprintf("%s=%s", d.name, d.Is)
|
||||
}
|
||||
|
||||
// TimeField type
|
||||
type TimeField struct {
|
||||
name string
|
||||
Is time.Time
|
||||
}
|
||||
|
||||
// String .
|
||||
func (d TimeField) String() string {
|
||||
return fmt.Sprintf("%s=%s", d.name, d.Is.UTC().Format(format))
|
||||
}
|
||||
|
||||
// AddTextField .
|
||||
func (s *Filter) AddTextField(field string) *TextField {
|
||||
f := &TextField{name: field}
|
||||
s.fields = append(s.fields, f)
|
||||
return f
|
||||
}
|
||||
|
||||
// AddTimeField .
|
||||
func (s *Filter) AddTimeField(field string) *TimeField {
|
||||
f := &TimeField{name: field}
|
||||
s.fields = append(s.fields, f)
|
||||
return f
|
||||
}
|
44
filter_test.go
Normal file
44
filter_test.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package paypal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFilter_AddTextField(t *testing.T) {
|
||||
filter := &Filter{}
|
||||
filter.AddTextField("sort_by").Is = "create_time"
|
||||
filter.AddTextField("count").Is = "30"
|
||||
filter.AddTextField("sort_order").Is = "desc"
|
||||
|
||||
expected := "?sort_by=create_time&count=30&sort_order=desc"
|
||||
if filter.String() != expected {
|
||||
t.Errorf("filter string was %s, wanted %s", filter.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_AddTimeField(t *testing.T) {
|
||||
filter := &Filter{}
|
||||
startTime := time.Time{}
|
||||
endTime := startTime.Add(time.Hour * 24 * 30)
|
||||
filter.AddTimeField("start_time").Is = startTime
|
||||
filter.AddTimeField("stop_time").Is = endTime
|
||||
|
||||
expected := "?start_time=0001-01-01T00:00:00Z&stop_time=0001-01-31T00:00:00Z"
|
||||
if filter.String() != expected {
|
||||
t.Errorf("filter string was %s, wanted %s", filter.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_AddMixedFields(t *testing.T) {
|
||||
filter := &Filter{}
|
||||
startTime := time.Time{}
|
||||
endTime := startTime.Add(time.Hour * 24 * 30)
|
||||
filter.AddTimeField("stop_time").Is = endTime
|
||||
filter.AddTextField("count").Is = "30"
|
||||
|
||||
expected := "?stop_time=0001-01-31T00:00:00Z&count=30"
|
||||
if filter.String() != expected {
|
||||
t.Errorf("filter string was %s, wanted %s", filter.String(), expected)
|
||||
}
|
||||
}
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
module euphoria-laxis.fr/go-packages/paypale/v4
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/stretchr/testify v1.9.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal 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=
|
66
identity.go
66
identity.go
|
@ -1,72 +1,24 @@
|
|||
package paypalsdk
|
||||
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 string, redirectURI string) (*TokenResponse, error) {
|
||||
token := &TokenResponse{}
|
||||
|
||||
q := url.Values{}
|
||||
q.Set("grant_type", "authorization_code")
|
||||
q.Set("code", code)
|
||||
q.Set("redirect_uri", redirectURI)
|
||||
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/identity/openidconnect/tokenservice"), strings.NewReader(q.Encode()))
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
if err = c.SendWithBasicAuth(req, token); err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GrantNewAccessTokenFromRefreshToken - Use this call to grant a new access token, using a refresh token.
|
||||
// Endpoint: POST /v1/identity/openidconnect/tokenservice
|
||||
func (c *Client) GrantNewAccessTokenFromRefreshToken(refreshToken string) (*TokenResponse, error) {
|
||||
type request struct {
|
||||
GrantType string `json:"grant_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
token := &TokenResponse{}
|
||||
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/identity/openidconnect/tokenservice"), request{GrantType: "refresh_token", RefreshToken: refreshToken})
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, token); err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetUserInfo - Use this call to retrieve user profile attributes.
|
||||
// Endpoint: GET /v1/identity/openidconnect/userinfo/?schema=<Schema>
|
||||
// Pass the schema that is used to return as per openidconnect protocol. The only supported schema value is openid.
|
||||
func (c *Client) GetUserInfo(schema string) (*UserInfo, error) {
|
||||
u := UserInfo{}
|
||||
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
|
||||
return u, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, &u); err != nil {
|
||||
return &u, err
|
||||
if err = c.SendWithAuth(req, u); err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
return u, nil
|
||||
}
|
||||
|
|
|
@ -1,437 +0,0 @@
|
|||
// +build integration
|
||||
|
||||
package paypalsdk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// All test values are defined here
|
||||
var testClientID = "AZgwu4yt5Ba0gyTu1dGBH3txHCJbMuFNvrmQxBaQbfDncDiCs6W_rwJD8Ir-0pZrN-_eq7n9zVd8Y-5f"
|
||||
var testSecret = "EBzA1wRl5t73OMugOieDj_tI3vihfJmGl47ukQT-cpctooIzDu0K7IPESNC0cKodlLSOXzwI8qXSM0rd"
|
||||
var testAuthID = "2DC87612EK520411B"
|
||||
var testOrderID = "O-0PW72302W3743444R"
|
||||
var testSaleID = "4CF18861HF410323U"
|
||||
var testPaymentID = "PAY-5YK922393D847794YKER7MUI"
|
||||
var testPayerID = "CR87QHB7JTRSC"
|
||||
var testUserID = "https://www.paypal.com/webapps/auth/identity/user/WEssgRpQij92sE99_F9MImvQ8FPYgUEjrvCja2qH2H8"
|
||||
var testCardID = "CARD-54E6956910402550WKGRL6EA"
|
||||
|
||||
func TestGetAccessToken(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
token, err := c.GetAccessToken()
|
||||
if err != nil {
|
||||
t.Errorf("Not expected error for GetAccessToken(), got %s", err.Error())
|
||||
}
|
||||
if token.Token == "" {
|
||||
t.Errorf("Expected non-empty token for GetAccessToken()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAuthorization(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.GetAuthorization(testAuthID)
|
||||
if err == nil {
|
||||
t.Errorf("GetAuthorization expects error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptureAuthorization(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.CaptureAuthorization(testAuthID, &Amount{Total: "200", Currency: "USD"}, true)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Auth is expired, 400 error must be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoidAuthorization(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.VoidAuthorization(testAuthID)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Auth is expired, 400 error must be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReauthorizeAuthorization(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.ReauthorizeAuthorization(testAuthID, &Amount{Total: "200", Currency: "USD"})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Reauthorization not allowed for this product, 500 error must be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrantNewAccessTokenFromAuthCode(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
|
||||
_, err := c.GrantNewAccessTokenFromAuthCode("123", "http://example.com/myapp/return.php")
|
||||
if err == nil {
|
||||
t.Errorf("GrantNewAccessTokenFromAuthCode must return error for invalid code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrantNewAccessTokenFromRefreshToken(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
|
||||
_, err := c.GrantNewAccessTokenFromRefreshToken("123")
|
||||
if err == nil {
|
||||
t.Errorf("GrantNewAccessTokenFromRefreshToken must return error for invalid refresh token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserInfo(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
u, err := c.GetUserInfo("openid")
|
||||
if u.ID != testUserID || err != nil {
|
||||
t.Errorf("GetUserInfo must return valid test ID=%s, error: %v", testUserID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrder(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.GetOrder(testOrderID)
|
||||
if err == nil {
|
||||
t.Errorf("GetOrder expects error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeOrder(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.AuthorizeOrder(testOrderID, &Amount{Total: "7.00", Currency: "USD"})
|
||||
if err == nil {
|
||||
t.Errorf("Order is expired, 400 error must be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptureOrder(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.CaptureOrder(testOrderID, &Amount{Total: "100", Currency: "USD"}, true, nil)
|
||||
if err == nil {
|
||||
t.Errorf("Order is expired, 400 error must be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoidOrder(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.VoidOrder(testOrderID)
|
||||
if err == nil {
|
||||
t.Errorf("Order is expired, 400 error must be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDirectPaypalPayment(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
amount := Amount{
|
||||
Total: "15.11",
|
||||
Currency: "USD",
|
||||
}
|
||||
|
||||
p, err := c.CreateDirectPaypalPayment(amount, "http://example.com", "http://example.com", "test payment")
|
||||
|
||||
if err != nil || p.ID == "" {
|
||||
t.Errorf("Test paypal payment is not created, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePayment(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
p := Payment{
|
||||
Intent: "sale",
|
||||
Payer: &Payer{
|
||||
PaymentMethod: "credit_card",
|
||||
FundingInstruments: []FundingInstrument{{
|
||||
CreditCard: &CreditCard{
|
||||
Number: "4111111111111111",
|
||||
Type: "visa",
|
||||
ExpireMonth: "11",
|
||||
ExpireYear: "2020",
|
||||
CVV2: "777",
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
},
|
||||
}},
|
||||
},
|
||||
Transactions: []Transaction{{
|
||||
Amount: &Amount{
|
||||
Currency: "USD",
|
||||
Total: "10.00", // total cost including shipping
|
||||
Details: Details{
|
||||
Shipping: "3.00", // total shipping cost
|
||||
Subtotal: "7.00", // total cost without shipping
|
||||
},
|
||||
},
|
||||
Description: "My Payment",
|
||||
ItemList: &ItemList{
|
||||
Items: []Item{
|
||||
Item{
|
||||
Quantity: "2",
|
||||
Price: "3.50",
|
||||
Currency: "USD",
|
||||
Name: "Product 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
RedirectURLs: &RedirectURLs{
|
||||
ReturnURL: "http://..",
|
||||
CancelURL: "http://..",
|
||||
},
|
||||
}
|
||||
_, err := c.CreatePayment(p)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPayment(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.GetPayment(testPaymentID)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("404 for this payment ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPayments(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.GetPayments()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Nil error expected, got: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchPayment(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
p := Payment{
|
||||
Intent: "sale",
|
||||
Payer: &Payer{
|
||||
PaymentMethod: "paypal",
|
||||
},
|
||||
Transactions: []Transaction{{
|
||||
Amount: &Amount{
|
||||
Currency: "USD",
|
||||
Total: "10.00", // total cost including shipping
|
||||
Details: Details{
|
||||
Shipping: "3.00", // total shipping cost
|
||||
Subtotal: "7.00", // total cost without shipping
|
||||
},
|
||||
},
|
||||
Description: "My Payment",
|
||||
ItemList: &ItemList{
|
||||
Items: []Item{
|
||||
Item{
|
||||
Quantity: "2",
|
||||
Price: "3.50",
|
||||
Currency: "USD",
|
||||
Name: "Product 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Custom: "First value",
|
||||
}},
|
||||
RedirectURLs: &RedirectURLs{
|
||||
ReturnURL: "http://..",
|
||||
CancelURL: "http://..",
|
||||
},
|
||||
}
|
||||
NewPayment, err := c.CreatePayment(p)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
PaymentID := NewPayment.ID
|
||||
pp := []PaymentPatch{
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/transactions/0/custom",
|
||||
Value: "Replaced Value",
|
||||
},
|
||||
}
|
||||
RevisedPayment, errpp := c.PatchPayment(PaymentID, pp)
|
||||
if errpp != nil {
|
||||
t.Errorf("Unexpected error when patching %v", errpp)
|
||||
}
|
||||
if RevisedPayment.Transactions != nil &&
|
||||
len(RevisedPayment.Transactions) > 0 &&
|
||||
RevisedPayment.Transactions[0].Custom != "" {
|
||||
if RevisedPayment.Transactions[0].Custom != "Replaced Value" {
|
||||
t.Errorf("Patched payment value failed to be patched %v", RevisedPayment.Transactions[0].Custom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteApprovedPayment(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.ExecuteApprovedPayment(testPaymentID, testPayerID)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("404 for this payment ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSinglePayout(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
payout := Payout{
|
||||
SenderBatchHeader: &SenderBatchHeader{
|
||||
EmailSubject: "Subject will be displayed on PayPal",
|
||||
},
|
||||
Items: []PayoutItem{
|
||||
{
|
||||
RecipientType: "EMAIL",
|
||||
Receiver: "single-email-payout@mail.com",
|
||||
Amount: &AmountPayout{
|
||||
Value: "15.11",
|
||||
Currency: "USD",
|
||||
},
|
||||
Note: "Optional note",
|
||||
SenderItemID: "Optional Item ID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := c.CreateSinglePayout(payout)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Test single payout is not created, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSale(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.GetSale(testSaleID)
|
||||
if err == nil {
|
||||
t.Errorf("404 must be returned for ID=%s", testSaleID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefundSale(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.RefundSale(testSaleID, nil)
|
||||
if err == nil {
|
||||
t.Errorf("404 must be returned for ID=%s", testSaleID)
|
||||
}
|
||||
|
||||
_, err = c.RefundSale(testSaleID, &Amount{Total: "7.00", Currency: "USD"})
|
||||
if err == nil {
|
||||
t.Errorf("404 must be returned for ID=%s", testSaleID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRefund(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
_, err := c.GetRefund("1")
|
||||
if err == nil {
|
||||
t.Errorf("404 must be returned for ID=%s", testSaleID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreCreditCard(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
r1, e1 := c.StoreCreditCard(CreditCard{})
|
||||
if e1 == nil || r1 != nil {
|
||||
t.Errorf("Error is expected for invalid CC")
|
||||
}
|
||||
|
||||
r2, e2 := c.StoreCreditCard(CreditCard{
|
||||
Number: "4417119669820331",
|
||||
Type: "visa",
|
||||
ExpireMonth: "11",
|
||||
ExpireYear: "2020",
|
||||
CVV2: "874",
|
||||
FirstName: "Foo",
|
||||
LastName: "Bar",
|
||||
})
|
||||
if e2 != nil || r2 == nil {
|
||||
t.Errorf("200 code expected for valid CC card. Error: %v", e2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCreditCard(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
e1 := c.DeleteCreditCard("")
|
||||
if e1 == nil {
|
||||
t.Errorf("Error is expected for invalid CC ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCreditCard(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
r1, e1 := c.GetCreditCard("BBGGG")
|
||||
if e1 == nil || r1 != nil {
|
||||
t.Errorf("Error is expected for invalid CC, got CC %v", r1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCreditCards(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
r1, e1 := c.GetCreditCards(nil)
|
||||
if e1 != nil || r1 == nil {
|
||||
t.Errorf("200 code expected. Error: %v", e1)
|
||||
}
|
||||
|
||||
r2, e2 := c.GetCreditCards(&CreditCardsFilter{
|
||||
Page: 2,
|
||||
PageSize: 7,
|
||||
})
|
||||
if e2 != nil || r2 == nil {
|
||||
t.Errorf("200 code expected. Error: %v", e2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchCreditCard(t *testing.T) {
|
||||
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
|
||||
c.GetAccessToken()
|
||||
|
||||
r1, e1 := c.PatchCreditCard(testCardID, nil)
|
||||
if e1 == nil || r1 != nil {
|
||||
t.Errorf("Error is expected for empty update info")
|
||||
}
|
||||
}
|
38
invoicing.go
Normal file
38
invoicing.go
Normal 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
406
invoicing_test.go
Normal 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)
|
||||
}
|
191
order.go
191
order.go
|
@ -1,13 +1,17 @@
|
|||
package paypalsdk
|
||||
package paypal
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GetOrder retrieves order by ID
|
||||
// Endpoint: GET /v1/payments/orders/ID
|
||||
func (c *Client) GetOrder(orderID string) (*Order, error) {
|
||||
// Endpoint: GET /v2/checkout/orders/ID
|
||||
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, "/v1/payments/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,16 +23,79 @@ func (c *Client) GetOrder(orderID string) (*Order, error) {
|
|||
return order, nil
|
||||
}
|
||||
|
||||
// AuthorizeOrder - Use this call to authorize an order.
|
||||
// Endpoint: POST /v1/payments/orders/ID/authorize
|
||||
func (c *Client) AuthorizeOrder(orderID string, amount *Amount) (*Authorization, error) {
|
||||
type authRequest struct {
|
||||
Amount *Amount `json:"amount"`
|
||||
// Create an order
|
||||
// Endpoint: POST /v2/checkout/orders
|
||||
func (c *Client) CreateOrder(ctx context.Context, intent string, purchaseUnits []PurchaseUnitRequest, paymentSource *PaymentSource, appContext *ApplicationContext) (*Order, error) {
|
||||
return c.CreateOrderWithPaypalRequestID(ctx, intent, purchaseUnits, paymentSource, appContext, "")
|
||||
}
|
||||
|
||||
// CreateOrderWithPaypalRequestID - Use this call to create an order with idempotency
|
||||
// Endpoint: POST /v2/checkout/orders
|
||||
func (c *Client) CreateOrderWithPaypalRequestID(ctx context.Context,
|
||||
intent string,
|
||||
purchaseUnits []PurchaseUnitRequest,
|
||||
paymentSource *PaymentSource,
|
||||
appContext *ApplicationContext,
|
||||
requestID string,
|
||||
) (*Order, error) {
|
||||
type createOrderRequest struct {
|
||||
Intent string `json:"intent"`
|
||||
PaymentSource *PaymentSource `json:"payment_source,omitempty"`
|
||||
PurchaseUnits []PurchaseUnitRequest `json:"purchase_units"`
|
||||
ApplicationContext *ApplicationContext `json:"application_context,omitempty"`
|
||||
}
|
||||
|
||||
auth := &Authorization{}
|
||||
order := &Order{}
|
||||
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/orders/"+orderID+"/authorize"), authRequest{Amount: amount})
|
||||
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders"), createOrderRequest{Intent: intent, PurchaseUnits: purchaseUnits, PaymentSource: paymentSource, ApplicationContext: appContext})
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
if requestID != "" {
|
||||
req.Header.Set("PayPal-Request-Id", requestID)
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, order); err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// UpdateOrder updates the order by ID
|
||||
// Endpoint: PATCH /v2/checkout/orders/ID
|
||||
func (c *Client) UpdateOrder(ctx context.Context, orderID string, op string, path string, value map[string]string) error {
|
||||
|
||||
type patchRequest struct {
|
||||
Op string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value map[string]string `json:"value"`
|
||||
}
|
||||
|
||||
req, err := c.NewRequest(ctx, "PATCH", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/checkout/orders/", orderID), []patchRequest{
|
||||
{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Value: value,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthorizeOrder - https://developer.paypal.com/docs/api/orders/v2/#orders_authorize
|
||||
// Endpoint: POST /v2/checkout/orders/ID/authorize
|
||||
func (c *Client) AuthorizeOrder(ctx context.Context, orderID string, authorizeOrderRequest AuthorizeOrderRequest) (*AuthorizeOrderResponse, error) {
|
||||
auth := &AuthorizeOrderResponse{}
|
||||
|
||||
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/authorize"), authorizeOrderRequest)
|
||||
if err != nil {
|
||||
return auth, err
|
||||
}
|
||||
|
@ -40,22 +107,42 @@ func (c *Client) AuthorizeOrder(orderID string, amount *Amount) (*Authorization,
|
|||
return auth, nil
|
||||
}
|
||||
|
||||
// CaptureOrder - Use this call to capture a payment on an order. To use this call, an original payment call must specify an intent of order.
|
||||
// Endpoint: POST /v1/payments/orders/ID/capture
|
||||
func (c *Client) CaptureOrder(orderID string, amount *Amount, isFinalCapture bool, currency *Currency) (*Capture, error) {
|
||||
type captureRequest struct {
|
||||
Amount *Amount `json:"amount"`
|
||||
IsFinalCapture bool `json:"is_final_capture"`
|
||||
Currency *Currency `json:"transaction_fee"`
|
||||
}
|
||||
// CaptureOrder - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
|
||||
// Endpoint: POST /v2/checkout/orders/ID/capture
|
||||
func (c *Client) CaptureOrder(ctx context.Context, orderID string, captureOrderRequest CaptureOrderRequest) (*CaptureOrderResponse, error) {
|
||||
return c.CaptureOrderWithPaypalRequestId(ctx, orderID, captureOrderRequest, "", nil)
|
||||
}
|
||||
|
||||
capture := &Capture{}
|
||||
// CaptureOrder with idempotency - https://developer.paypal.com/docs/api/orders/v2/#orders_capture
|
||||
// Endpoint: POST /v2/checkout/orders/ID/capture
|
||||
// https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
|
||||
func (c *Client) CaptureOrderWithPaypalRequestId(ctx context.Context,
|
||||
orderID string,
|
||||
captureOrderRequest CaptureOrderRequest,
|
||||
requestID string,
|
||||
mockResponse *CaptureOrderMockResponse,
|
||||
) (*CaptureOrderResponse, error) {
|
||||
capture := &CaptureOrderResponse{}
|
||||
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/orders/"+orderID+"/capture"), captureRequest{Amount: amount, IsFinalCapture: isFinalCapture, Currency: currency})
|
||||
c.SetReturnRepresentation()
|
||||
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/checkout/orders/"+orderID+"/capture"), captureOrderRequest)
|
||||
if err != nil {
|
||||
return capture, err
|
||||
}
|
||||
|
||||
if requestID != "" {
|
||||
req.Header.Set("PayPal-Request-Id", requestID)
|
||||
}
|
||||
|
||||
if mockResponse != nil {
|
||||
mock, err := json.Marshal(mockResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("PayPal-Mock-Response", string(mock))
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, capture); err != nil {
|
||||
return capture, err
|
||||
}
|
||||
|
@ -63,20 +150,48 @@ func (c *Client) CaptureOrder(orderID string, amount *Amount, isFinalCapture boo
|
|||
return capture, nil
|
||||
}
|
||||
|
||||
// VoidOrder - Use this call to void an existing order.
|
||||
// Note: An order cannot be voided if payment has already been partially or fully captured.
|
||||
// Endpoint: POST /v1/payments/orders/ID/do-void
|
||||
func (c *Client) VoidOrder(orderID string) (*Order, error) {
|
||||
order := &Order{}
|
||||
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/orders/"+orderID+"/do-void"), nil)
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, order); err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
return order, nil
|
||||
// RefundCapture - https://developer.paypal.com/docs/api/payments/v2/#captures_refund
|
||||
// Endpoint: POST /v2/payments/captures/ID/refund
|
||||
func (c *Client) RefundCapture(ctx context.Context, captureID string, refundCaptureRequest RefundCaptureRequest) (*RefundResponse, error) {
|
||||
return c.RefundCaptureWithPaypalRequestId(ctx, captureID, refundCaptureRequest, "")
|
||||
}
|
||||
|
||||
// RefundCapture with idempotency - https://developer.paypal.com/docs/api/payments/v2/#captures_refund
|
||||
// Endpoint: POST /v2/payments/captures/ID/refund
|
||||
func (c *Client) RefundCaptureWithPaypalRequestId(ctx context.Context,
|
||||
captureID string,
|
||||
refundCaptureRequest RefundCaptureRequest,
|
||||
requestID string,
|
||||
) (*RefundResponse, error) {
|
||||
refund := &RefundResponse{}
|
||||
|
||||
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/captures/"+captureID+"/refund"), refundCaptureRequest)
|
||||
if err != nil {
|
||||
return refund, err
|
||||
}
|
||||
|
||||
if requestID != "" {
|
||||
req.Header.Set("PayPal-Request-Id", requestID)
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, refund); err != nil {
|
||||
return refund, err
|
||||
}
|
||||
return refund, nil
|
||||
}
|
||||
|
||||
// CapturedDetail - https://developer.paypal.com/docs/api/payments/v2/#captures_get
|
||||
// Endpoint: GET /v2/payments/captures/ID
|
||||
func (c *Client) CapturedDetail(ctx context.Context, captureID string) (*CaptureDetailsResponse, error) {
|
||||
response := &CaptureDetailsResponse{}
|
||||
|
||||
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v2/payments/captures/"+captureID), nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
|
146
payment.go
146
payment.go
|
@ -1,146 +0,0 @@
|
|||
package paypalsdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ListPaymentsResp slice of payments
|
||||
type ListPaymentsResp struct {
|
||||
Payments []Payment `json:"payments"`
|
||||
}
|
||||
|
||||
// CreatePaymentResp contains Payment Info and Links slice
|
||||
type CreatePaymentResp struct {
|
||||
*Payment
|
||||
Links []Link `json:"links"`
|
||||
}
|
||||
|
||||
// CreateDirectPaypalPayment sends request to create a payment with payment_method=paypal
|
||||
// CreatePayment is more common function for any kind of payment
|
||||
// Endpoint: POST /v1/payments/payment
|
||||
func (c *Client) CreateDirectPaypalPayment(amount Amount, redirectURI string, cancelURI string, description string) (*PaymentResponse, error) {
|
||||
buf := bytes.NewBuffer([]byte("{\"intent\":\"sale\",\"payer\":{\"payment_method\":\"paypal\"}," +
|
||||
"\"transactions\":[{\"amount\":{\"total\":\"" + amount.Total +
|
||||
"\",\"currency\":\"" + amount.Currency + "\"},\"description\":\"" + description + "\"}],\"redirect_urls\":{\"return_url\":\"" +
|
||||
redirectURI + "\",\"cancel_url\":\"" + cancelURI + "\"}}"))
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment"), buf)
|
||||
if err != nil {
|
||||
return &PaymentResponse{}, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(c.ClientID, c.Secret)
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
|
||||
|
||||
p := PaymentResponse{}
|
||||
|
||||
if err = c.SendWithAuth(req, &p); err != nil {
|
||||
return &p, err
|
||||
}
|
||||
|
||||
if p.ID == "" {
|
||||
return &p, errors.New("Unable to create payment with this access token")
|
||||
}
|
||||
|
||||
return &p, err
|
||||
}
|
||||
|
||||
// CreatePayment creates a payment in Paypal
|
||||
// Depending on the payment_method and the funding_instrument, you can use the payment resource for direct credit card payments, stored credit card payments, or PayPal account payments.
|
||||
// Endpoint: POST /v1/payments/payment
|
||||
func (c *Client) CreatePayment(p Payment) (*CreatePaymentResp, error) {
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment"), p)
|
||||
if err != nil {
|
||||
return &CreatePaymentResp{}, err
|
||||
}
|
||||
|
||||
response := &CreatePaymentResp{}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ExecuteApprovedPayment - Use this call to execute (complete) a PayPal payment that has been approved by the payer. You can optionally update transaction information when executing the payment by passing in one or more transactions.
|
||||
// Endpoint: POST /v1/payments/payment/paymentID/execute
|
||||
func (c *Client) ExecuteApprovedPayment(paymentID string, payerID string) (*ExecuteResponse, error) {
|
||||
buf := bytes.NewBuffer([]byte("{\"payer_id\":\"" + payerID + "\"}"))
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment/"+paymentID+"/execute"), buf)
|
||||
if err != nil {
|
||||
return &ExecuteResponse{}, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(c.ClientID, c.Secret)
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
|
||||
|
||||
e := ExecuteResponse{}
|
||||
|
||||
if err = c.SendWithAuth(req, &e); err != nil {
|
||||
return &e, err
|
||||
}
|
||||
|
||||
if e.ID == "" {
|
||||
return &e, errors.New("Unable to execute payment with paymentID=" + paymentID)
|
||||
}
|
||||
|
||||
return &e, err
|
||||
}
|
||||
|
||||
// GetPayment gets a payment from PayPal
|
||||
// Endpoint: GET /v1/payments/payment/ID
|
||||
func (c *Client) GetPayment(paymentID string) (*Payment, error) {
|
||||
p := Payment{}
|
||||
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment/"+paymentID), nil)
|
||||
if err != nil {
|
||||
return &p, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, &p); err != nil {
|
||||
return &p, err
|
||||
}
|
||||
|
||||
if p.ID == "" {
|
||||
return &p, errors.New("Unable to get payment with paymentID=" + paymentID)
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// PatchPayment modifies some fields of a payment prior to execution
|
||||
// Endpoint: PATCH /v1/payments/payment/ID
|
||||
func (c *Client) PatchPayment(paymentID string, p []PaymentPatch) (*Payment, error) {
|
||||
req, err := c.NewRequest("PATCH", fmt.Sprintf("%s/v1/payments/payment/%s", c.APIBase, paymentID), p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := Payment{}
|
||||
|
||||
if err = c.SendWithAuth(req, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetPayments retrieve payments resources from Paypal
|
||||
// Endpoint: GET /v1/payments/payment/
|
||||
func (c *Client) GetPayments() ([]Payment, error) {
|
||||
var p ListPaymentsResp
|
||||
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/payment/"), nil)
|
||||
if err != nil {
|
||||
return p.Payments, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, &p); err != nil {
|
||||
return p.Payments, err
|
||||
}
|
||||
|
||||
return p.Payments, nil
|
||||
}
|
44
payout.go
44
payout.go
|
@ -1,20 +1,21 @@
|
|||
package paypalsdk
|
||||
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)
|
||||
if err != nil {
|
||||
return &PayoutResponse{}, err
|
||||
}
|
||||
|
||||
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 {
|
||||
return response, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
@ -25,15 +26,14 @@ 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 {
|
||||
return &PayoutResponse{}, err
|
||||
return response, err
|
||||
}
|
||||
|
||||
response := &PayoutResponse{}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
@ -44,15 +44,14 @@ 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 {
|
||||
return &PayoutItemResponse{}, err
|
||||
return response, err
|
||||
}
|
||||
|
||||
response := &PayoutItemResponse{}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
@ -63,15 +62,14 @@ 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 {
|
||||
return &PayoutItemResponse{}, err
|
||||
return response, err
|
||||
}
|
||||
|
||||
response := &PayoutItemResponse{}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
|
119
products.go
Normal file
119
products.go
Normal 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
|
||||
}
|
54
sale.go
54
sale.go
|
@ -1,55 +1,17 @@
|
|||
package paypalsdk
|
||||
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.
|
||||
// Endpoint: POST /v1/payments/sale/ID/refund
|
||||
func (c *Client) RefundSale(saleID string, a *Amount) (*Refund, error) {
|
||||
type refundRequest struct {
|
||||
Amount *Amount `json:"amount"`
|
||||
}
|
||||
|
||||
refund := &Refund{}
|
||||
|
||||
req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/sale/"+saleID+"/refund"), &refundRequest{Amount: a})
|
||||
if err != nil {
|
||||
return refund, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, refund); err != nil {
|
||||
return refund, err
|
||||
}
|
||||
|
||||
return refund, nil
|
||||
}
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GetRefund by ID
|
||||
// Use it to look up details of a specific refund on direct and captured payments.
|
||||
// Endpoint: GET /v1/payments/refund/ID
|
||||
func (c *Client) GetRefund(refundID string) (*Refund, error) {
|
||||
// Endpoint: GET /v2/payments/refund/ID
|
||||
func (c *Client) GetRefund(ctx context.Context, refundID string) (*Refund, error) {
|
||||
refund := &Refund{}
|
||||
|
||||
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/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
|
||||
}
|
||||
|
|
234
subscription.go
Normal file
234
subscription.go
Normal file
|
@ -0,0 +1,234 @@
|
|||
package paypal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
SubscriptionBase struct {
|
||||
PlanID string `json:"plan_id"`
|
||||
StartTime *JSONTime `json:"start_time,omitempty"`
|
||||
Quantity string `json:"quantity,omitempty"`
|
||||
ShippingAmount *Money `json:"shipping_amount,omitempty"`
|
||||
Subscriber *Subscriber `json:"subscriber,omitempty"`
|
||||
AutoRenewal bool `json:"auto_renewal,omitempty"`
|
||||
ApplicationContext *ApplicationContext `json:"application_context,omitempty"`
|
||||
CustomID string `json:"custom_id,omitempty"`
|
||||
Plan *PlanOverride `json:"plan,omitempty"`
|
||||
}
|
||||
|
||||
SubscriptionDetails struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
SubscriptionStatus SubscriptionStatus `json:"status,omitempty"`
|
||||
SubscriptionStatusChangeNote string `json:"status_change_note,omitempty"`
|
||||
StatusUpdateTime time.Time `json:"status_update_time,omitempty"`
|
||||
}
|
||||
|
||||
Subscription struct {
|
||||
SubscriptionDetailResp
|
||||
}
|
||||
|
||||
// SubscriptionDetailResp struct
|
||||
SubscriptionDetailResp struct {
|
||||
SubscriptionBase
|
||||
SubscriptionDetails
|
||||
BillingInfo BillingInfo `json:"billing_info,omitempty"` // not found in documentation
|
||||
SharedResponse
|
||||
}
|
||||
|
||||
SubscriptionCaptureResponse struct {
|
||||
Status SubscriptionTransactionStatus `json:"status"`
|
||||
Id string `json:"id"`
|
||||
AmountWithBreakdown AmountWithBreakdown `json:"amount_with_breakdown"`
|
||||
PayerName Name `json:"payer_name"`
|
||||
PayerEmail string `json:"payer_email"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
//Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-amount_with_breakdown
|
||||
AmountWithBreakdown struct {
|
||||
GrossAmount Money `json:"gross_amount"`
|
||||
FeeAmount Money `json:"fee_amount"`
|
||||
ShippingAmount Money `json:"shipping_amount"`
|
||||
TaxAmount Money `json:"tax_amount"`
|
||||
NetAmount Money `json:"net_amount"`
|
||||
}
|
||||
|
||||
SubscriptionTransactionsParams struct {
|
||||
SubscriptionId string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
SubscriptionTransactionsResponse struct {
|
||||
Transactions []SubscriptionCaptureResponse `json:"transactions"`
|
||||
SharedListResponse
|
||||
}
|
||||
|
||||
CaptureRequest struct {
|
||||
Note string `json:"note"`
|
||||
CaptureType CaptureType `json:"capture_type"`
|
||||
Amount Money `json:"amount"`
|
||||
}
|
||||
|
||||
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-plan_override
|
||||
PlanOverride struct {
|
||||
BillingCycles []BillingCycleOverride `json:"billing_cycles,omitempty"`
|
||||
PaymentPreferences *PaymentPreferencesOverride `json:"payment_preferences,omitempty"`
|
||||
Taxes *TaxesOverride `json:"taxes,omitempty"`
|
||||
}
|
||||
|
||||
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-payment_preferences_override
|
||||
PaymentPreferencesOverride struct {
|
||||
AutoBillOutstanding bool `json:"auto_bill_outstanding,omitempty"`
|
||||
SetupFee Money `json:"setup_fee,omitempty"`
|
||||
SetupFeeFailureAction SetupFeeFailureAction `json:"setup_fee_failure_action,omitempty"`
|
||||
PaymentFailureThreshold int `json:"payment_failure_threshold,omitempty"`
|
||||
}
|
||||
|
||||
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-payment_preferences_override
|
||||
TaxesOverride struct {
|
||||
Percentage string `json:"percentage,omitempty"`
|
||||
Inclusive *bool `json:"inclusive,omitempty"`
|
||||
}
|
||||
|
||||
// https://developer.paypal.com/docs/api/subscriptions/v1/#definition-billing_cycle_override
|
||||
BillingCycleOverride struct {
|
||||
PricingScheme PricingScheme `json:"pricing_scheme,omitempty"`
|
||||
Sequence *int `json:"sequence,omitempty"`
|
||||
TotalCycles *int `json:"total_cycles,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func (p *Subscription) GetUpdatePatch() []Patch {
|
||||
result := []Patch{
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/billing_info/outstanding_balance",
|
||||
Value: p.BillingInfo.OutstandingBalance,
|
||||
},
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CreateSubscriptionPlan creates a subscriptionPlan
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
|
||||
// Endpoint: POST /v1/billing/subscriptions
|
||||
func (c *Client) CreateSubscription(ctx context.Context, newSubscription SubscriptionBase) (*SubscriptionDetailResp, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/billing/subscriptions"), newSubscription)
|
||||
req.Header.Add("Prefer", "return=representation")
|
||||
response := &SubscriptionDetailResp{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = c.SendWithAuth(req, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// UpdateSubscriptionPlan. updates a plan
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_patch
|
||||
// Endpoint: PATCH /v1/billing/subscriptions/:subscription_id
|
||||
func (c *Client) UpdateSubscription(ctx context.Context, updatedSubscription Subscription) error {
|
||||
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/billing/subscriptions/", updatedSubscription.ID), updatedSubscription.GetUpdatePatch())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.SendWithAuth(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSubscriptionDetails shows details for a subscription, by ID.
|
||||
// Endpoint: GET /v1/billing/subscriptions/
|
||||
func (c *Client) GetSubscriptionDetails(ctx context.Context, subscriptionID string) (*SubscriptionDetailResp, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/billing/subscriptions/%s", c.APIBase, subscriptionID), nil)
|
||||
response := &SubscriptionDetailResp{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = c.SendWithAuth(req, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// Activates the subscription.
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_activate
|
||||
// Endpoint: POST /v1/billing/subscriptions/{id}/activate
|
||||
func (c *Client) ActivateSubscription(ctx context.Context, subscriptionId, activateReason string) error {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/activate", c.APIBase, subscriptionId), map[string]string{"reason": activateReason})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.SendWithAuth(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Cancels the subscription.
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
|
||||
// Endpoint: POST /v1/billing/subscriptions/{id}/cancel
|
||||
func (c *Client) CancelSubscription(ctx context.Context, subscriptionId, cancelReason string) error {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/cancel", c.APIBase, subscriptionId), map[string]string{"reason": cancelReason})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.SendWithAuth(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Captures an authorized payment from the subscriber on the subscription.
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_capture
|
||||
// Endpoint: POST /v1/billing/subscriptions/{id}/capture
|
||||
func (c *Client) CaptureSubscription(ctx context.Context, subscriptionId string, request CaptureRequest) (*SubscriptionCaptureResponse, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/capture", c.APIBase, subscriptionId), request)
|
||||
response := &SubscriptionCaptureResponse{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = c.SendWithAuth(req, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// Suspends the subscription.
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_suspend
|
||||
// Endpoint: POST /v1/billing/subscriptions/{id}/suspend
|
||||
func (c *Client) SuspendSubscription(ctx context.Context, subscriptionId, reason string) error {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/suspend", c.APIBase, subscriptionId), map[string]string{"reason": reason})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.SendWithAuth(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Lists transactions for a subscription.
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_transactions
|
||||
// Endpoint: GET /v1/billing/subscriptions/{id}/transactions
|
||||
func (c *Client) GetSubscriptionTransactions(ctx context.Context, requestParams SubscriptionTransactionsParams) (*SubscriptionTransactionsResponse, error) {
|
||||
startTime := requestParams.StartTime.Format("2006-01-02T15:04:05Z")
|
||||
endTime := requestParams.EndTime.Format("2006-01-02T15:04:05Z")
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/billing/subscriptions/%s/transactions?start_time=%s&end_time=%s", c.APIBase, requestParams.SubscriptionId, startTime, endTime), nil)
|
||||
response := &SubscriptionTransactionsResponse{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// Revise plan or quantity of subscription
|
||||
// Doc: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_revise
|
||||
// Endpoint: POST /v1/billing/subscriptions/{id}/revise
|
||||
func (c *Client) ReviseSubscription(ctx context.Context, subscriptionId string, reviseSubscription SubscriptionBase) (*SubscriptionDetailResp, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/v1/billing/subscriptions/%s/revise", c.APIBase, subscriptionId), reviseSubscription)
|
||||
response := &SubscriptionDetailResp{}
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
err = c.SendWithAuth(req, response)
|
||||
|
||||
return response, err
|
||||
}
|
233
subscription_plan.go
Normal file
233
subscription_plan.go
Normal 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
|
||||
}
|
96
transaction_search.go
Normal file
96
transaction_search.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package paypal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TransactionSearchRequest struct {
|
||||
TransactionID *string
|
||||
TransactionType *string
|
||||
TransactionStatus *string
|
||||
TransactionAmount *string
|
||||
TransactionCurrency *string
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
PaymentInstrumentType *string
|
||||
StoreID *string
|
||||
TerminalID *string
|
||||
Fields *string
|
||||
BalanceAffectingRecordsOnly *string
|
||||
PageSize *int
|
||||
Page *int
|
||||
}
|
||||
|
||||
type TransactionSearchResponse struct {
|
||||
TransactionDetails []SearchTransactionDetails `json:"transaction_details"`
|
||||
AccountNumber string `json:"account_number"`
|
||||
StartDate JSONTime `json:"start_date"`
|
||||
EndDate JSONTime `json:"end_date"`
|
||||
LastRefreshDatetime JSONTime `json:"last_refreshed_datetime"`
|
||||
Page int `json:"page"`
|
||||
SharedListResponse
|
||||
}
|
||||
|
||||
// ListTransactions - Use this to search PayPal transactions from the last 31 days.
|
||||
// Endpoint: GET /v1/reporting/transactions
|
||||
func (c *Client) ListTransactions(ctx context.Context, req *TransactionSearchRequest) (*TransactionSearchResponse, error) {
|
||||
response := &TransactionSearchResponse{}
|
||||
|
||||
r, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/reporting/transactions"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
|
||||
q.Add("start_date", req.StartDate.Format(time.RFC3339))
|
||||
q.Add("end_date", req.EndDate.Format(time.RFC3339))
|
||||
|
||||
if req.TransactionID != nil {
|
||||
q.Add("transaction_id", *req.TransactionID)
|
||||
}
|
||||
if req.TransactionType != nil {
|
||||
q.Add("transaction_type", *req.TransactionType)
|
||||
}
|
||||
if req.TransactionStatus != nil {
|
||||
q.Add("transaction_status", *req.TransactionStatus)
|
||||
}
|
||||
if req.TransactionAmount != nil {
|
||||
q.Add("transaction_amount", *req.TransactionAmount)
|
||||
}
|
||||
if req.TransactionCurrency != nil {
|
||||
q.Add("transaction_currency", *req.TransactionCurrency)
|
||||
}
|
||||
if req.PaymentInstrumentType != nil {
|
||||
q.Add("payment_instrument_type", *req.PaymentInstrumentType)
|
||||
}
|
||||
if req.StoreID != nil {
|
||||
q.Add("store_id", *req.StoreID)
|
||||
}
|
||||
if req.TerminalID != nil {
|
||||
q.Add("terminal_id", *req.TerminalID)
|
||||
}
|
||||
if req.Fields != nil {
|
||||
q.Add("fields", *req.Fields)
|
||||
}
|
||||
if req.BalanceAffectingRecordsOnly != nil {
|
||||
q.Add("balance_affecting_records_only", *req.BalanceAffectingRecordsOnly)
|
||||
}
|
||||
if req.PageSize != nil {
|
||||
q.Add("page_size", strconv.Itoa(*req.PageSize))
|
||||
}
|
||||
if req.Page != nil {
|
||||
q.Add("page", strconv.Itoa(*req.Page))
|
||||
}
|
||||
|
||||
r.URL.RawQuery = q.Encode()
|
||||
|
||||
if err = c.SendWithAuth(r, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
556
unit_test.go
556
unit_test.go
|
@ -1,17 +1,10 @@
|
|||
package paypalsdk
|
||||
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 {
|
||||
|
@ -57,8 +50,6 @@ func TestTypeUserInfo(t *testing.T) {
|
|||
func TestTypeItem(t *testing.T) {
|
||||
response := `{
|
||||
"name":"Item",
|
||||
"price":"22.99",
|
||||
"currency":"GBP",
|
||||
"quantity":"1"
|
||||
}`
|
||||
|
||||
|
@ -69,8 +60,6 @@ func TestTypeItem(t *testing.T) {
|
|||
}
|
||||
|
||||
if i.Name != "Item" ||
|
||||
i.Price != "22.99" ||
|
||||
i.Currency != "GBP" ||
|
||||
i.Quantity != "1" {
|
||||
t.Errorf("Item decoded result is incorrect, Given: %v", i)
|
||||
}
|
||||
|
@ -144,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":{
|
||||
|
@ -228,6 +246,134 @@ func TestTypePayoutResponse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOrderUnmarshal(t *testing.T) {
|
||||
response := `{
|
||||
"id": "5O190127TN364715T",
|
||||
"status": "CREATED",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.paypal.com/v2/checkout/orders/5O190127TN364715T",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/checkoutnow?token=5O190127TN364715T",
|
||||
"rel": "approve",
|
||||
"method": "GET"
|
||||
},
|
||||
{
|
||||
"href": "https://api.paypal.com/v2/checkout/orders/5O190127TN364715T/capture",
|
||||
"rel": "capture",
|
||||
"method": "POST"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
order := &Order{}
|
||||
err := json.Unmarshal([]byte(response), order)
|
||||
if err != nil {
|
||||
t.Errorf("Order Unmarshal failed")
|
||||
}
|
||||
|
||||
if order.ID != "5O190127TN364715T" ||
|
||||
order.Status != "CREATED" ||
|
||||
order.Links[0].Href != "https://api.paypal.com/v2/checkout/orders/5O190127TN364715T" {
|
||||
t.Errorf("Order decoded result is incorrect, Given: %+v", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderCompletedUnmarshal(t *testing.T) {
|
||||
response := `{
|
||||
"id": "1K412082HD5737736",
|
||||
"status": "COMPLETED",
|
||||
"purchase_units": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "99.99"
|
||||
},
|
||||
"payee": {
|
||||
"email_address": "payee@business.example.com",
|
||||
"merchant_id": "7DVPP5Q2RZJQY"
|
||||
},
|
||||
"custom_id": "123456",
|
||||
"soft_descriptor": "PAYPAL *TEST STORE",
|
||||
"shipping": {
|
||||
"name": {
|
||||
"full_name": "John Doe"
|
||||
},
|
||||
"address": {
|
||||
"address_line_1": "Address, Country",
|
||||
"admin_area_2": "Area2",
|
||||
"admin_area_1": "Area1",
|
||||
"postal_code": "123456",
|
||||
"country_code": "US"
|
||||
}
|
||||
},
|
||||
"payments": {
|
||||
"captures": [
|
||||
{
|
||||
"id": "6V864560EH247264J",
|
||||
"status": "COMPLETED",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "99.99"
|
||||
},
|
||||
"final_capture": true,
|
||||
"custom_id": "123456",
|
||||
"create_time": "2021-07-27T09:39:17Z",
|
||||
"update_time": "2021-07-27T09:39:17Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "John",
|
||||
"surname": "Doe"
|
||||
},
|
||||
"email_address": "payer@personal.example.com",
|
||||
"payer_id": "7D36CJQ2TUEUU",
|
||||
"address": {
|
||||
"address_line_1": "City, Country",
|
||||
"admin_area_2": "Area2",
|
||||
"admin_area_1": "Area1",
|
||||
"postal_code": "123456",
|
||||
"country_code": "US"
|
||||
}
|
||||
},
|
||||
"create_time": "2021-07-27T09:38:37Z",
|
||||
"update_time": "2021-07-27T09:39:17Z",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/1K412082HD5737736",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
order := &Order{}
|
||||
err := json.Unmarshal([]byte(response), order)
|
||||
if err != nil {
|
||||
t.Errorf("Order Unmarshal failed")
|
||||
}
|
||||
|
||||
if order.ID != "1K412082HD5737736" ||
|
||||
order.Status != "COMPLETED" ||
|
||||
order.PurchaseUnits[0].Payee.EmailAddress != "payee@business.example.com" ||
|
||||
order.PurchaseUnits[0].CustomID != "123456" ||
|
||||
order.PurchaseUnits[0].Shipping.Name.FullName != "John Doe" ||
|
||||
order.PurchaseUnits[0].Shipping.Address.AdminArea1 != "Area1" ||
|
||||
order.Payer.Name.GivenName != "John" ||
|
||||
order.Payer.Address.AddressLine1 != "City, Country" ||
|
||||
order.Links[0].Href != "https://api.sandbox.paypal.com/v2/checkout/orders/1K412082HD5737736" {
|
||||
t.Errorf("Order decoded result is incorrect, Given: %+v", order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypePayoutItemResponse(t *testing.T) {
|
||||
response := `{
|
||||
"payout_item_id":"9T35G83YA546X",
|
||||
|
@ -325,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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
47
vault.go
47
vault.go
|
@ -1,30 +1,31 @@
|
|||
package paypalsdk
|
||||
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
|
||||
}
|
||||
|
||||
response := CreditCard{}
|
||||
response := &CreditCard{}
|
||||
|
||||
if err = c.SendWithAuth(req, &response); err != nil {
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// DeleteCreditCard func
|
||||
// Endpoint: DELETE /v1/vault/credit-cards/credit_card_id
|
||||
func (c *Client) DeleteCreditCard(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,24 +39,24 @@ 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
|
||||
}
|
||||
|
||||
response := CreditCard{}
|
||||
response := &CreditCard{}
|
||||
|
||||
if err = c.SendWithAuth(req, &response); err != nil {
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetCreditCards func
|
||||
// Endpoint: GET /v1/vault/credit-cards
|
||||
func (c *Client) GetCreditCards(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,33 +66,33 @@ 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
|
||||
}
|
||||
|
||||
response := CreditCards{}
|
||||
response := &CreditCards{}
|
||||
|
||||
if err = c.SendWithAuth(req, &response); err != nil {
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// PatchCreditCard func
|
||||
// Endpoint: PATCH /v1/vault/credit-cards/credit_card_id
|
||||
func (c *Client) PatchCreditCard(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
|
||||
}
|
||||
|
||||
response := CreditCard{}
|
||||
response := &CreditCard{}
|
||||
|
||||
if err = c.SendWithAuth(req, &response); err != nil {
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
return response, nil
|
||||
}
|
||||
|
|
144
webhooks.go
Normal file
144
webhooks.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package paypal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CreateWebhook - Subscribes your webhook listener to events.
|
||||
// Endpoint: POST /v1/notifications/webhooks
|
||||
func (c *Client) CreateWebhook(ctx context.Context, createWebhookRequest *CreateWebhookRequest) (*Webhook, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), createWebhookRequest)
|
||||
webhook := &Webhook{}
|
||||
if err != nil {
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, webhook)
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
// GetWebhook - Shows details for a webhook, by ID.
|
||||
// Endpoint: GET /v1/notifications/webhooks/ID
|
||||
func (c *Client) GetWebhook(ctx context.Context, webhookID string) (*Webhook, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/notifications/webhooks/", webhookID), nil)
|
||||
webhook := &Webhook{}
|
||||
if err != nil {
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, webhook)
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
// UpdateWebhook - Updates a webhook to replace webhook fields with new values.
|
||||
// Endpoint: PATCH /v1/notifications/webhooks/ID
|
||||
func (c *Client) UpdateWebhook(ctx context.Context, webhookID string, fields []WebhookField) (*Webhook, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), fields)
|
||||
webhook := &Webhook{}
|
||||
if err != nil {
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, webhook)
|
||||
return webhook, err
|
||||
}
|
||||
|
||||
// ListWebhooks - Lists webhooks for an app.
|
||||
// Endpoint: GET /v1/notifications/webhooks
|
||||
func (c *Client) ListWebhooks(ctx context.Context, anchorType string) (*ListWebhookResponse, error) {
|
||||
if len(anchorType) == 0 {
|
||||
anchorType = AncorTypeApplication
|
||||
}
|
||||
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks"), nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("anchor_type", anchorType)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
resp := &ListWebhookResponse{}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DeleteWebhook - Deletes a webhook, by ID.
|
||||
// Endpoint: DELETE /v1/notifications/webhooks/ID
|
||||
func (c *Client) DeleteWebhook(ctx context.Context, webhookID string) error {
|
||||
req, err := c.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/v1/notifications/webhooks/%s", c.APIBase, webhookID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// VerifyWebhookSignature - Use this to verify the signature of a webhook recieved from paypal.
|
||||
// Endpoint: POST /v1/notifications/verify-webhook-signature
|
||||
func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Request, webhookID string) (*VerifyWebhookResponse, error) {
|
||||
type verifyWebhookSignatureRequest struct {
|
||||
AuthAlgo string `json:"auth_algo,omitempty"`
|
||||
CertURL string `json:"cert_url,omitempty"`
|
||||
TransmissionID string `json:"transmission_id,omitempty"`
|
||||
TransmissionSig string `json:"transmission_sig,omitempty"`
|
||||
TransmissionTime string `json:"transmission_time,omitempty"`
|
||||
WebhookID string `json:"webhook_id,omitempty"`
|
||||
Event json.RawMessage `json:"webhook_event,omitempty"`
|
||||
}
|
||||
|
||||
// Read the content
|
||||
var bodyBytes []byte
|
||||
if httpReq.Body != nil {
|
||||
bodyBytes, _ = io.ReadAll(httpReq.Body)
|
||||
} else {
|
||||
return nil, errors.New("cannot verify webhook for HTTP Request with empty body")
|
||||
}
|
||||
// Restore the io.ReadCloser to its original state
|
||||
httpReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
|
||||
verifyRequest := verifyWebhookSignatureRequest{
|
||||
AuthAlgo: httpReq.Header.Get("PAYPAL-AUTH-ALGO"),
|
||||
CertURL: httpReq.Header.Get("PAYPAL-CERT-URL"),
|
||||
TransmissionID: httpReq.Header.Get("PAYPAL-TRANSMISSION-ID"),
|
||||
TransmissionSig: httpReq.Header.Get("PAYPAL-TRANSMISSION-SIG"),
|
||||
TransmissionTime: httpReq.Header.Get("PAYPAL-TRANSMISSION-TIME"),
|
||||
WebhookID: webhookID,
|
||||
Event: json.RawMessage(bodyBytes),
|
||||
}
|
||||
|
||||
response := &VerifyWebhookResponse{}
|
||||
|
||||
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/verify-webhook-signature"), verifyRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetWebhookEventTypes - Lists all webhook event types.
|
||||
// Endpoint: GET /v1/notifications/webhooks-event-types
|
||||
func (c *Client) GetWebhookEventTypes(ctx context.Context) (*WebhookEventTypesResponse, error) {
|
||||
req, err := c.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s%s", c.APIBase, "/v1/notifications/webhooks-event-types"), nil)
|
||||
q := req.URL.Query()
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
resp := &WebhookEventTypesResponse{}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.SendWithAuth(req, resp)
|
||||
return resp, err
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package paypalsdk
|
||||
package paypal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -10,15 +11,15 @@ 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)
|
||||
if err != nil {
|
||||
return &WebProfile{}, err
|
||||
}
|
||||
|
||||
req, err := c.NewRequest(ctx, "POST", url, wp)
|
||||
response := &WebProfile{}
|
||||
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
if err = c.SendWithAuth(req, response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -44,20 +45,20 @@ func (c *Client) GetWebProfile(profileID string) (*WebProfile, error) {
|
|||
}
|
||||
|
||||
if wp.ID == "" {
|
||||
return &wp, fmt.Errorf("paypalsdk: unable to get web profile with ID = %s", profileID)
|
||||
return &wp, fmt.Errorf("paypal: unable to get web profile with ID = %s", profileID)
|
||||
}
|
||||
|
||||
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,15 +74,15 @@ 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("paypalsdk: no ID specified for WebProfile")
|
||||
return fmt.Errorf("paypal: no ID specified for WebProfile")
|
||||
}
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user