Remove deprecated endpoints

This commit is contained in:
Alex Pliutau 2024-06-03 09:53:55 +02:00
parent e5db87f4a3
commit 6ebb0352bc
14 changed files with 36 additions and 1010 deletions

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2021 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

113
README.md
View File

@ -2,112 +2,13 @@
# Go client for PayPal REST API
## Coverage
## Paypal REST API Docs
### Auth
[Get started with PayPal REST APIs](https://developer.paypal.com/api/rest/)
* POST /v1/oauth2/token
### /v1/payments
* POST /v1/payments/payouts
* GET /v1/payments/payouts/:id
* GET /v1/payments/payouts-item/:id
* POST /v1/payments/payouts-item/:id/cancel
* GET /v1/payments/sale/:id
* POST /v1/payments/sale/:id/refund
* GET /v1/payments/billing-plans
* POST /v1/payments/billing-plans
* PATCH /v1/payments/billing-plans/:id
* POST /v1/payments/billing-agreements
* POST /v1/payments/billing-agreements/:token/agreement-execute
### /v2/payments
* GET /v2/payments/authorizations/:id
* GET /v2/payments/captures/:id
* POST /v2/payments/authorizations/:id/capture
* POST /v2/payments/authorizations/:id/void
* POST /v2/payments/authorizations/:id/reauthorize
* GET /v2/payments/refund/:id
### Identity
* POST /v1/identity/openidconnect/tokenservice
* GET /v1/identity/openidconnect/userinfo/?schema=:schema
### /v1/payment-experience
* 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
### /v1/reporting
* POST /v1/reporting/transactions
### Vault
* 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
### Checkout
* POST /v2/checkout/orders
* GET /v2/checkout/orders/:id
* PATCH /v2/checkout/orders/:id
* POST /v2/checkout/orders/:id/authorize
* POST /v2/checkout/orders/:id/capture
### Notifications
* POST /v1/notifications/webhooks
* GET /v1/notifications/webhooks
* GET /v1/notifications/webhooks/:id
* PATCH /v1/notifications/webhooks/:id
* DELETE /v1/notifications/webhooks/:id
* POST /v1/notifications/verify-webhook-signature
### Products (Catalog)
* POST /v1/catalogs/products
* PATCH /v1/catalogs/products/:id
* GET /v1/catalogs/products/:id
* GET /v1/catalogs/products
### Billing Plans (Subscriptions)
* POST /v1/billing/plans
* PATCH /v1/billing/plans/:id
* GET /v1/billing/plans/:id
* GET /v1/billing/plans
* POST /v1/billing/plans/:id/activate
* POST /v1/billing/plans/:id/deactivate
* POST /v1/billing/plans/:id/update-pricing-schemes
### Subscriptions
* POST /v1/billing/subscriptions
* PATCH /v1/billing/subscriptions/:id
* GET /v1/billing/subscriptions/:id
* POST /v1/billing/subscriptions/:id/activate
* POST /v1/billing/subscriptions/:id/cancel
* POST /v1/billing/subscriptions/:id/revise
* POST /v1/billing/subscriptions/:id/capture
* POST /v1/billing/subscriptions/:id/suspend
* GET /v1/billing/subscriptions/:id/transactions
### Invoicing
* POST /v2/invoicing/generate-next-invoice-number
* GET /v2/invoicing/invoices/:id
## 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`
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
@ -373,16 +274,19 @@ 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 \
@ -407,5 +311,6 @@ Current contributors:
## Tests
* Unit tests: `go test -v ./...`
* Integration tests: `go test -tags=integration`
```
go test -v ./...
```

View File

@ -1,161 +0,0 @@
package paypal
import (
"context"
"errors"
"fmt"
"net/http"
"time"
)
type (
// CreateBillingResponse struct
CreateBillingResponse 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"`
}
// CreateBillingResp.
//
// Deprecated: use CreateBillingResponse instead.
CreateBillingResp = CreateBillingResponse
// CreateAgreementResponse struct
CreateAgreementResponse 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"`
}
// CreateAgreementResp.
//
// Deprecated: use CreateAgreementResponse instead.
CreateAgreementResp = CreateAgreementResponse
// BillingPlanListParams
BillingPlanListParams struct {
ListParams
Status string `json:"status,omitempty"` //Allowed values: CREATED, ACTIVE, INACTIVE, ALL.
}
// BillingPlanListResponse
BillingPlanListResponse struct {
SharedListResponse
Plans []BillingPlan `json:"plans,omitempty"`
}
// BillingPlanListResp.
//
// Deprecated: use BillingPlanListResponse instead.
BillingPlanListResp = BillingPlanListResponse
)
// CreateBillingPlan creates a billing plan in Paypal
// Endpoint: POST /v1/payments/billing-plans
func (c *Client) CreateBillingPlan(ctx context.Context, plan BillingPlan) (*CreateBillingResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), plan)
response := &CreateBillingResponse{}
if err != nil {
return response, err
}
err = c.SendWithAuth(req, response)
return response, err
}
// UpdateBillingPlan updates values inside a billing plan
// Endpoint: PATCH /v1/payments/billing-plans
func (c *Client) UpdateBillingPlan(ctx context.Context, planId string, pathValues map[string]map[string]interface{}) error {
var patchData []Patch
for path, data := range pathValues {
patchData = append(patchData, Patch{
Operation: "replace",
Path: path,
Value: data,
})
}
req, err := c.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payments/billing-plans/", planId), patchData)
if err != nil {
return err
}
err = c.SendWithAuth(req, nil)
return err
}
// ActivatePlan activates a billing plan
// By default, a new plan is not activated
// Endpoint: PATCH /v1/payments/billing-plans/
func (c *Client) ActivatePlan(ctx context.Context, planID string) error {
return c.UpdateBillingPlan(ctx, planID, map[string]map[string]interface{}{
"/": {"state": BillingPlanStatusActive},
})
}
// CreateBillingAgreement creates an agreement for specified plan
// Endpoint: POST /v1/payments/billing-agreements
// Deprecated: Use POST /v1/billing-agreements/agreements
func (c *Client) CreateBillingAgreement(ctx context.Context, a BillingAgreement) (*CreateAgreementResponse, 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(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements"), a)
response := &CreateAgreementResponse{}
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(ctx context.Context, token string) (*ExecuteAgreementResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/v1/payments/billing-agreements/%s/agreement-execute", c.APIBase, token), nil)
response := &ExecuteAgreementResponse{}
if err != nil {
return response, err
}
req.SetBasicAuth(c.ClientID, c.Secret)
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
if err = c.SendWithAuth(req, response); err != nil {
return response, err
}
if response.ID == "" {
return response, errors.New("Unable to execute agreement with token=" + token)
}
return response, err
}
// ListBillingPlans lists billing-plans
// Endpoint: GET /v1/payments/billing-plans
func (c *Client) ListBillingPlans(ctx context.Context, bplp BillingPlanListParams) (*BillingPlanListResponse, error) {
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), nil)
response := &BillingPlanListResponse{}
if err != nil {
return response, err
}
q := req.URL.Query()
q.Add("page", bplp.Page)
q.Add("page_size", bplp.PageSize)
q.Add("status", bplp.Status)
q.Add("total_required", bplp.TotalRequired)
req.URL.RawQuery = q.Encode()
err = c.SendWithAuth(req, response)
return response, err
}

View File

@ -1,115 +0,0 @@
package paypal
import (
"context"
"fmt"
)
// CreatePaypalBillingAgreementToken - Use this call to create a billing agreement token
// Endpoint: POST /v1/billing-agreements/agreement-tokens
// Deprecated: use CreateBillingAgreementToken instead
func (c *Client) CreatePaypalBillingAgreementToken(
ctx context.Context,
description *string,
shippingAddress *ShippingAddress,
payer *Payer,
plan *BillingPlan,
) (*BillingAgreementToken, error) {
return c.CreateBillingAgreementToken(ctx, description, shippingAddress, payer, plan)
}
// CreateBillingAgreementToken - Use this call to create a billing agreement token
// Endpoint: POST /v1/billing-agreements/agreement-tokens
func (c *Client) CreateBillingAgreementToken(
ctx context.Context,
description *string,
shippingAddress *ShippingAddress,
payer *Payer,
plan *BillingPlan,
) (*BillingAgreementToken, error) {
type createBARequest struct {
Description *string `json:"description,omitempty"`
ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"`
Payer *Payer `json:"payer"`
Plan *BillingPlan `json:"plan"`
}
billingAgreementToken := &BillingAgreementToken{}
req, err := c.NewRequest(
ctx,
"POST",
fmt.Sprintf("%s%s", c.APIBase, "/v1/billing-agreements/agreement-tokens"),
createBARequest{Description: description, ShippingAddress: shippingAddress, Payer: payer, Plan: plan})
if err != nil {
return nil, err
}
if err = c.SendWithAuth(req, billingAgreementToken); err != nil {
return billingAgreementToken, err
}
return billingAgreementToken, nil
}
// CreatePaypalBillingAgreementFromToken - Use this call to create a billing agreement
// Endpoint: POST /v1/billing-agreements/agreements
// Deprecated: use CreateBillingAgreementFromToken instead
func (c *Client) CreatePaypalBillingAgreementFromToken(
ctx context.Context,
tokenID string,
) (*BillingAgreementFromToken, error) {
return c.CreateBillingAgreementFromToken(ctx, tokenID)
}
// CreateBillingAgreementFromToken - Use this call to create a billing agreement
// Endpoint: POST /v1/billing-agreements/agreements
func (c *Client) CreateBillingAgreementFromToken(
ctx context.Context,
tokenID string,
) (*BillingAgreementFromToken, error) {
type createBARequest struct {
TokenID string `json:"token_id"`
}
billingAgreement := &BillingAgreementFromToken{}
req, err := c.NewRequest(
ctx,
"POST",
fmt.Sprintf("%s%s", c.APIBase, "/v1/billing-agreements/agreements"),
createBARequest{TokenID: tokenID})
if err != nil {
return nil, err
}
if err = c.SendWithAuth(req, billingAgreement); err != nil {
return billingAgreement, err
}
return billingAgreement, nil
}
// CancelBillingAgreement - Use this call to cancel a billing agreement
// Endpoint: POST /v1/billing-agreements/agreements/{agreement_id}/cancel
func (c *Client) CancelBillingAgreement(
ctx context.Context,
billingAgreementID string,
) error {
type cancelBARequest struct{}
req, err := c.NewRequest(
ctx,
"POST",
fmt.Sprintf("%s%s%s%s", c.APIBase, "/v1/billing-agreements/agreements/", billingAgreementID, "/cancel"),
cancelBARequest{})
if err != nil {
return err
}
if err = c.SendWithAuth(req, nil); err != nil {
return err
}
return nil
}

View File

@ -1,23 +0,0 @@
package paypal
import (
"context"
"fmt"
)
// GetCapturedPaymentDetails.
// Endpoint: GET /v1/payments/capture/:id
func (c *Client) GetCapturedPaymentDetails(ctx context.Context, id string) (*Capture, error) {
res := &Capture{}
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payments/capture/", id), nil)
if err != nil {
return res, err
}
if err = c.SendWithAuth(req, res); err != nil {
return res, err
}
return res, nil
}

View File

@ -15,6 +15,9 @@ import (
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)

10
go.mod
View File

@ -1,5 +1,11 @@
module github.com/plutov/paypal/v4
go 1.13
go 1.22
require github.com/stretchr/testify v1.6.0
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
)

13
go.sum
View File

@ -1,11 +1,10 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,417 +0,0 @@
// +build integration
package paypal
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// All test values are defined here
var testClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
var testSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
var testUserID = "https://www.paypal.com/webapps/auth/identity/user/VBqgHcgZwb1PBs69ybjjXfIW86_Hr93aBvF_Rgbh2II"
var testCardID = "CARD-54E6956910402550WKGRL6EA"
var testProductId = "" // will be fetched in func TestProduct(t *testing.T)
var testBillingPlan = "" // will be fetched in func TestSubscriptionPlans(t *testing.T)
func TestGetAccessToken(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
token, err := c.GetAccessToken(context.Background())
if err != nil {
t.Errorf("Not expected error for GetAccessToken(), got %s", err.Error())
}
if token.Token == "" {
t.Errorf("Expected non-empty token for GetAccessToken()")
}
}
func TestGetUserInfo(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
u, err := c.GetUserInfo(context.Background(), "openid")
if u.ID != testUserID || err != nil {
t.Errorf("GetUserInfo must return valid test ID %s, got %s, error: %v", testUserID, u.ID, err)
}
}
func TestCreateVenmoPayout(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
payout := Payout{
SenderBatchHeader: &SenderBatchHeader{
SenderBatchID: "Payouts_2018_100007",
EmailSubject: "You have a payout!",
EmailMessage: "You have received a payout! Thanks for using our service!",
},
Items: []PayoutItem{
{
RecipientType: "EMAIL",
RecipientWallet: VenmoRecipientWallet,
Receiver: "receiver@example.com",
Amount: &AmountPayout{
Value: "9.87",
Currency: "USD",
},
Note: "Thanks for your patronage!",
SenderItemID: "201403140001",
},
},
}
res, err := c.CreatePayout(context.Background(), payout)
assert.NoError(t, err, "should accept venmo wallet")
assert.Greater(t, len(res.Items), 0)
}
func TestCreatePayout(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
payout := Payout{
SenderBatchHeader: &SenderBatchHeader{
SenderBatchID: "Payouts_2018_100007",
EmailSubject: "You have a payout!",
EmailMessage: "You have received a payout! Thanks for using our service!",
},
Items: []PayoutItem{
{
RecipientType: "EMAIL",
Receiver: "receiver@example.com",
Amount: &AmountPayout{
Value: "9.87",
Currency: "USD",
},
Note: "Thanks for your patronage!",
SenderItemID: "201403140001",
},
},
}
c.CreatePayout(context.Background(), payout)
}
func TestStoreCreditCard(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
r1, e1 := c.StoreCreditCard(context.Background(), CreditCard{})
if e1 == nil || r1 != nil {
t.Errorf("Error is expected for invalid CC")
}
r2, e2 := c.StoreCreditCard(context.Background(), 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(context.Background())
e1 := c.DeleteCreditCard(context.Background(), "")
if e1 == nil {
t.Errorf("Error is expected for invalid CC ID")
}
}
func TestGetCreditCard(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
r1, e1 := c.GetCreditCard(context.Background(), "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(context.Background())
r1, e1 := c.GetCreditCards(context.Background(), nil)
if e1 != nil || r1 == nil {
t.Errorf("200 code expected. Error: %v", e1)
}
r2, e2 := c.GetCreditCards(context.Background(), &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(context.Background())
r1, e1 := c.PatchCreditCard(context.Background(), testCardID, nil)
if e1 == nil || r1 != nil {
t.Errorf("Error is expected for empty update info")
}
}
// Creates, gets, and deletes single webhook
func TestCreateAndGetWebhook(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
payload := &CreateWebhookRequest{
URL: "https://example.com/paypal_webhooks",
EventTypes: []WebhookEventType{
WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
}
createdWebhook, err := c.CreateWebhook(context.Background(), payload)
if err != nil {
t.Errorf("Webhook couldn't be created, error %v", err)
}
_, err = c.GetWebhook(context.Background(), createdWebhook.ID)
if err != nil {
t.Errorf("An error occurred while getting webhook, error %v", err)
}
err = c.DeleteWebhook(context.Background(), createdWebhook.ID)
if err != nil {
t.Errorf("An error occurred while webhooks deletion, error %v", err)
}
}
// Creates, updates, and deletes single webhook
func TestCreateAndUpdateWebhook(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
creationPayload := &CreateWebhookRequest{
URL: "https://example.com/paypal_webhooks",
EventTypes: []WebhookEventType{
WebhookEventType{
Name: "PAYMENT.AUTHORIZATION.CREATED",
},
},
}
createdWebhook, err := c.CreateWebhook(context.Background(), creationPayload)
if err != nil {
t.Errorf("Webhook couldn't be created, error %v", err)
}
updatePayload := []WebhookField{
WebhookField{
Operation: "replace",
Path: "/event_types",
Value: []interface{}{
map[string]interface{}{
"name": "PAYMENT.SALE.REFUNDED",
},
},
},
}
_, err = c.UpdateWebhook(context.Background(), createdWebhook.ID, updatePayload)
if err != nil {
t.Errorf("Couldn't update webhook, error %v", err)
}
err = c.DeleteWebhook(context.Background(), createdWebhook.ID)
if err != nil {
t.Errorf("An error occurred while webhooks deletion, error %v", err)
}
}
func TestListWebhooks(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
_, err := c.ListWebhooks(context.Background(), AncorTypeApplication)
if err != nil {
t.Errorf("Cannot registered list webhooks, error %v", err)
}
}
func TestProduct(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
//create a product
productData := Product{
Name: "Test Product",
Description: "A Test Product",
Category: ProductCategorySoftware,
Type: ProductTypeService,
ImageUrl: "https://example.com/image.png",
HomeUrl: "https://example.com",
}
productCreateResponse, err := c.CreateProduct(context.Background(), productData)
assert.Equal(t, nil, err)
testProductId = productCreateResponse.ID
//update the product
productData.ID = productCreateResponse.ID
productData.Description = "Updated product"
err = c.UpdateProduct(context.Background(), productData)
assert.Equal(t, nil, err)
//get product data
productFetched, err := c.GetProduct(context.Background(), productData.ID)
assert.Equal(t, nil, err)
assert.Equal(t, productFetched.Description, "Updated product")
//test that lising products have more than one product
productList, err := c.ListProducts(context.Background(), nil)
assert.Equal(t, nil, err)
assert.NotEqual(t, len(productList.Products), 0)
}
func TestSubscriptionPlans(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
//create a product
newSubscriptionPlan := SubscriptionPlan{
ProductId: testProductId,
Name: "Test subscription plan",
Status: SubscriptionPlanStatusCreated,
Description: "Integration test subscription plan",
BillingCycles: []BillingCycle{
{
PricingScheme: PricingScheme{
Version: 1,
FixedPrice: Money{
Currency: "EUR",
Value: "5",
},
CreateTime: time.Now(),
UpdateTime: time.Now(),
},
Frequency: Frequency{
IntervalUnit: IntervalUnitYear,
IntervalCount: 1,
},
TenureType: TenureTypeRegular,
Sequence: 1,
TotalCycles: 0,
},
},
PaymentPreferences: &PaymentPreferences{
AutoBillOutstanding: false,
SetupFee: nil,
SetupFeeFailureAction: SetupFeeFailureActionCancel,
PaymentFailureThreshold: 0,
},
Taxes: &Taxes{
Percentage: "19",
Inclusive: false,
},
QuantitySupported: false,
}
//test create new plan
planCreateResponse, err := c.CreateSubscriptionPlan(context.Background(), newSubscriptionPlan)
assert.Equal(t, nil, err)
testBillingPlan = planCreateResponse.ID // for next test
//test update the newly created plan
newSubscriptionPlan.ID = planCreateResponse.ID
newSubscriptionPlan.Description = "updated description"
err = c.UpdateSubscriptionPlan(context.Background(), newSubscriptionPlan)
assert.Equal(t, nil, err)
//test get plan information
existingPlan, err := c.GetSubscriptionPlan(context.Background(), newSubscriptionPlan.ID)
assert.Equal(t, nil, err)
assert.Equal(t, newSubscriptionPlan.Description, existingPlan.Description)
//test activate plan
err = c.ActivateSubscriptionPlan(context.Background(), newSubscriptionPlan.ID)
assert.Equal(t, nil, err)
//test deactivate plan
err = c.DeactivateSubscriptionPlans(context.Background(), newSubscriptionPlan.ID)
assert.Equal(t, nil, err)
//reactivate this plan for next next (subscription)
err = c.ActivateSubscriptionPlan(context.Background(), newSubscriptionPlan.ID)
assert.Equal(t, nil, err)
//test upadte plan pricing
err = c.UpdateSubscriptionPlanPricing(context.Background(), newSubscriptionPlan.ID, []PricingSchemeUpdate{
{
BillingCycleSequence: 1,
PricingScheme: PricingScheme{
Version: 1,
FixedPrice: Money{
Currency: "EUR",
Value: "6",
},
CreateTime: time.Now(),
UpdateTime: time.Now(),
},
},
})
assert.Equal(t, nil, err)
//test update pricing scheme
updatedPricingPlan, err := c.GetSubscriptionPlan(context.Background(), newSubscriptionPlan.ID)
assert.Equal(t, nil, err)
assert.Equal(t, "6.0", updatedPricingPlan.BillingCycles[0].PricingScheme.FixedPrice.Value)
}
func TestSubscription(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
newSubscription := SubscriptionBase{
PlanID: testBillingPlan,
}
//create new subscription
newSubResponse, err := c.CreateSubscription(context.Background(), newSubscription)
assert.Equal(t, nil, err)
assert.NotEqual(t, "", newSubResponse.ID)
//get subscription details
subDetails, err := c.GetSubscriptionDetails(context.Background(), newSubResponse.ID)
assert.Equal(t, nil, err)
assert.NotEqual(t, "", subDetails.ID)
}
func TestGetWebhookEventTypes(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
r, err := c.GetWebhookEventTypes(context.Background())
assert.Equal(t, nil, err)
assert.GreaterOrEqual(t, len(r.EventTypes), 1)
for _, v := range r.EventTypes {
assert.GreaterOrEqual(t, len(v.Name), 1)
assert.GreaterOrEqual(t, len(v.Description), 1)
assert.GreaterOrEqual(t, len(v.Status), 1)
}
}

View File

@ -1,66 +0,0 @@
package paypal
import (
"context"
"testing"
)
var testClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
var testSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
func TestUpdateOrder(t *testing.T) {
ctx := context.Background()
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
_, _ = c.GetAccessToken(ctx)
orderResponse, err := c.CreateOrder(
ctx,
OrderIntentCapture,
[]PurchaseUnitRequest{
{
Amount: &PurchaseUnitAmount{
Value: "7.00",
Currency: "USD",
},
},
},
&CreateOrderPayer{},
&ApplicationContext{},
)
if err != nil {
t.Errorf("Not expected error for CreateOrder(), got %s", err.Error())
}
order, err := c.GetOrder(ctx, orderResponse.ID)
if err != nil {
t.Errorf("Not expected error for GetOrder(), got %s", err.Error())
}
if order.PurchaseUnits[0].Amount.Value != "7.00" {
t.Errorf("CreateOrder amount incorrect")
}
err = c.UpdateOrder(
ctx,
orderResponse.ID,
"replace",
"/purchase_units/@reference_id=='default'/amount",
map[string]string{
"currency_code": "USD",
"value": "2.00",
},
)
if err != nil {
t.Errorf("Not expected error for UpdateOrder(), got %s", err.Error())
}
order, err = c.GetOrder(ctx, orderResponse.ID)
if err != nil {
t.Errorf("Not expected error for GetOrder(), got %s", err.Error())
}
if order.PurchaseUnits[0].Amount.Value != "2.00" {
t.Errorf("CreateOrder after update amount incorrect")
}
}

View File

@ -1,7 +0,0 @@
package paypal
type Patch struct {
Operation string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}

View File

@ -23,11 +23,6 @@ func (c *Client) CreatePayout(ctx context.Context, p Payout) (*PayoutResponse, e
return response, nil
}
// CreateSinglePayout is deprecated, use CreatePayout instead.
func (c *Client) CreateSinglePayout(ctx context.Context, p Payout) (*PayoutResponse, error) {
return c.CreatePayout(ctx, p)
}
// 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

41
sale.go
View File

@ -5,47 +5,6 @@ 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(ctx context.Context, saleID string) (*Sale, error) {
sale := &Sale{}
req, err := c.NewRequest(ctx, "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(ctx context.Context, saleID string, a *Amount) (*Refund, error) {
type refundRequest struct {
Amount *Amount `json:"amount"`
}
refund := &Refund{}
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/sale/"+saleID+"/refund"), &refundRequest{Amount: a})
if err != nil {
return refund, err
}
if err = c.SendWithAuth(req, refund); err != nil {
return refund, err
}
return refund, nil
}
// GetRefund by ID
// Use it to look up details of a specific refund on direct and captured payments.
// Endpoint: GET /v2/payments/refund/ID

View File

@ -587,32 +587,6 @@ type (
SenderBatchHeader *SenderBatchHeader `json:"sender_batch_header,omitempty"`
}
// BillingAgreement struct
BillingAgreement struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
StartDate JSONTime `json:"start_date,omitempty"`
Plan BillingPlan `json:"plan,omitempty"`
Payer Payer `json:"payer,omitempty"`
ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"`
OverrideMerchantPreferences *MerchantPreferences `json:"override_merchant_preferences,omitempty"`
}
// BillingAgreementFromToken struct
BillingAgreementFromToken struct {
ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
Payer *Payer `json:"payer,omitempty"`
Plan BillingPlan `json:"plan,omitempty"`
Links []Link `json:"links,omitempty"`
}
// BillingAgreementToken response struct
BillingAgreementToken struct {
Links []Link `json:"links,omitempty"`
TokenID string `json:"token_id,omitempty"`
}
// Plan struct
Plan struct {
ID string `json:"id"`
@ -1415,38 +1389,6 @@ type (
Value interface{} `json:"value"`
}
// Resource is a mix of fields from several webhook resource types.
//
// Deprecated: Add implementation of specific resource types in your own
// code and don't use this catch all struct, you show know which resource
// type you are expecting and handle that type only.
//
// Every resource struct type should be unique for every combination of
// "resource_type"/"resource_version" combination of the Event type /
// webhook message.
Resource struct {
ID string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
StatusDetails *CaptureStatusDetails `json:"status_details,omitempty"`
Amount *PurchaseUnitAmount `json:"amount,omitempty"`
UpdateTime string `json:"update_time,omitempty"`
CreateTime string `json:"create_time,omitempty"`
ExpirationTime string `json:"expiration_time,omitempty"`
SellerProtection *SellerProtection `json:"seller_protection,omitempty"`
FinalCapture bool `json:"final_capture,omitempty"`
SellerPayableBreakdown *CaptureSellerBreakdown `json:"seller_payable_breakdown,omitempty"`
SellerReceivableBreakdown *SellerReceivableBreakdown `json:"seller_receivable_breakdown,omitempty"`
NoteToPayer string `json:"note_to_payer,omitempty"`
CustomID string `json:"custom_id,omitempty"`
PartnerClientID string `json:"partner_client_id,omitempty"`
MerchantID string `json:"merchant_id,omitempty"`
Intent string `json:"intent,omitempty"`
BillingAgreementID *string `json:"billing_agreement_id,omitempty"`
PurchaseUnits []*PurchaseUnitRequest `json:"purchase_units,omitempty"`
Payer *PayerWithNameAndPhone `json:"payer,omitempty"`
Links []Link `json:"links,omitempty"`
}
CaptureSellerBreakdown struct {
GrossAmount PurchaseUnitAmount `json:"gross_amount"`
PayPalFee PurchaseUnitAmount `json:"paypal_fee"`
@ -1615,6 +1557,12 @@ type (
TotalPages int `json:"total_pages,omitempty"`
Links []Link `json:"links,omitempty"`
}
Patch struct {
Operation string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
)
// Error method implementation for ErrorResponse struct
@ -1652,6 +1600,6 @@ func (e *expirationTime) UnmarshalJSON(b []byte) error {
// Convert ExpirationTime to time.Duration
func (e *expirationTime) ToDuration() time.Duration {
seconds := int64(*e)
return time.Duration(seconds) * time.Second
}
seconds := int64(*e)
return time.Duration(seconds) * time.Second
}