Skip to content

Commit

Permalink
Subscriptions, Subscription plans and Products API (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
rami-dabain committed May 31, 2020
1 parent b3eb2c6 commit d355a65
Show file tree
Hide file tree
Showing 12 changed files with 883 additions and 66 deletions.
40 changes: 33 additions & 7 deletions README.md
Expand Up @@ -20,36 +20,62 @@ Currently supports **v2** only, if you want to use **v1**, use **v1.1.4** git ta
* GET /v1/payment-experience/web-profiles/**ID**
* PUT /v1/payment-experience/web-profiles/**ID**
* DELETE /v1/payment-experience/web-profiles/**ID**
* POST /v1/vault/credit-cards
* DELETE /v1/vault/credit-cards/**ID**
* PATCH /v1/vault/credit-cards/**ID**
* GET /v1/vault/credit-cards/**ID**
* GET /v1/vault/credit-cards
* GET /v2/payments/authorizations/**ID**
* POST /v2/payments/authorizations/**ID**/capture
* POST /v2/payments/authorizations/**ID**/void
* POST /v2/payments/authorizations/**ID**/reauthorize
* GET /v1/payments/sale/**ID**
* POST /v1/payments/sale/**ID**/refund
* GET /v2/payments/refund/**ID**
* POST /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
#Billing plans (payments)
* 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
#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
* POST /v1/reporting/transactions

#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**/capture
* POST /v1/billing/subscriptions/**ID**/suspend
* GET /v1/billing/subscriptions/**ID**/transactions

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

Expand Down
63 changes: 39 additions & 24 deletions billing.go
Expand Up @@ -2,6 +2,7 @@ package paypal

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -31,26 +32,21 @@ type (

// BillingPlanListParams struct
BillingPlanListParams struct {
Page string `json:"page,omitempty"` //Default: 0.
Status string `json:"status,omitempty"` //Allowed values: CREATED, ACTIVE, INACTIVE, ALL.
PageSize string `json:"page_size,omitempty"` //Default: 10.
TotalRequired string `json:"total_required,omitempty"` //Default: no.

ListParams
Status string `json:"status,omitempty"` //Allowed values: CREATED, ACTIVE, INACTIVE, ALL.
}

//BillingPlanListResp struct
BillingPlanListResp struct {
Plans []BillingPlan `json:"plans,omitempty"`
TotalItems string `json:"total_items,omitempty"`
TotalPages string `json:"total_pages,omitempty"`
Links []Link `json:"links,omitempty"`
SharedListResponse
Plans []BillingPlan `json:"plans,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)
req, err := c.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), plan)
response := &CreateBillingResp{}
if err != nil {
return response, err
Expand All @@ -59,18 +55,35 @@ func (c *Client) CreateBillingPlan(plan BillingPlan) (*CreateBillingResp, error)
return response, err
}

// UpdateBillingPlan updates values inside a billing plan
// Endpoint: PATCH /v1/payments/billing-plans
func (c *Client) UpdateBillingPlan(planId string, pathValues map[string]map[string]interface{}) error {
patchData := []Patch{}
for path, data := range pathValues {
patchData = append(patchData, Patch{
Operation: "replace",
Path: path,
Value: data,
})
}

jsonData, err := json.Marshal(patchData)
buf := bytes.NewBuffer(jsonData)
req, err := c.NewRequest(http.MethodPatch, fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payments/billing-plans/", planId), buf)
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(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)
return c.UpdateBillingPlan(planID, map[string]map[string]interface{}{
"/": {"state": BillingPlanStatusActive},
})
}

// CreateBillingAgreement creates an agreement for specified plan
Expand All @@ -81,7 +94,7 @@ func (c *Client) CreateBillingAgreement(a BillingAgreement) (*CreateAgreementRes
ID: a.Plan.ID,
}

req, err := c.NewRequest("POST", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements"), a)
req, err := c.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-agreements"), a)
response := &CreateAgreementResp{}
if err != nil {
return response, err
Expand All @@ -93,7 +106,7 @@ func (c *Client) CreateBillingAgreement(a BillingAgreement) (*CreateAgreementRes
// 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)
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/v1/payments/billing-agreements/%s/agreement-execute", c.APIBase, token), nil)
response := &ExecuteAgreementResponse{}

if err != nil {
Expand All @@ -118,16 +131,18 @@ func (c *Client) ExecuteApprovedAgreement(token string) (*ExecuteAgreementRespon
// Endpoint: GET /v1/payments/billing-plans
func (c *Client) ListBillingPlans(bplp BillingPlanListParams) (*BillingPlanListResp, error) {
req, err := c.NewRequest("GET", fmt.Sprintf("%s%s", c.APIBase, "/v1/payments/billing-plans"), nil)
response := &BillingPlanListResp{}
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()
response := &BillingPlanListResp{}
if err != nil {
return response, err
}

err = c.SendWithAuth(req, response)
return response, err
}
79 changes: 79 additions & 0 deletions const.go
@@ -0,0 +1,79 @@
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"
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"
)
2 changes: 2 additions & 0 deletions go.mod
@@ -1,3 +1,5 @@
module github.com/plutov/paypal/v3

go 1.12

require github.com/stretchr/testify v1.6.0
11 changes: 11 additions & 0 deletions go.sum
@@ -0,0 +1,11 @@
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/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=
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=

0 comments on commit d355a65

Please sign in to comment.