diff --git a/README.md b/README.md index 12692f3..65978f9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ * 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 ### 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** diff --git a/billing.go b/billing.go new file mode 100644 index 0000000..18a18cb --- /dev/null +++ b/billing.go @@ -0,0 +1,93 @@ +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 +} + +// 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) +} + +// Creates an agreement for specified plan +// Endpoint: POST /v1/payments/billing-agreements +func (c *Client) CreateBillingAgreement(a BillingAgreement) (*CreateAgreementResp, error) { + 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{} + err = c.SendWithAuth(req, &e) + if err != nil { + return &e, err + } + + if e.ID == "" { + return &e, errors.New("Unable to execute agreement with token=" + token) + } + + return &e, err +} diff --git a/billing_test.go b/billing_test.go new file mode 100644 index 0000000..6fb1f9b --- /dev/null +++ b/billing_test.go @@ -0,0 +1,107 @@ +package paypalsdk_test + +import ( + "fmt" + pp "github.com/logpacker/PayPal-Go-SDK" + "time" +) + +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) +} diff --git a/types.go b/types.go index 074a46f..cbaa1c4 100644 --- a/types.go +++ b/types.go @@ -44,6 +44,9 @@ const ( ) type ( + // JsonTime overrides MarshalJson method to format in ISO8601 + JsonTime time.Time + // Address struct Address struct { Line1 string `json:"line1"` @@ -55,6 +58,18 @@ type ( Phone string `json:"phone,omitempty"` } + // AgreementDetails struct + AgreementDetails struct { + OutstandingBalance AmountPayout `json:"outstanding_balance"` + CyclesRemaining int `json:"cycles_remaining,string"` + CyclesCompleted int `json:"cycles_completed,string"` + NextBillingDate time.Time `json:"next_billing_date"` + LastPaymentDate time.Time `json:"last_payment_date"` + LastPaymentAmount AmountPayout `json:"last_payment_amount"` + FinalPaymentDate time.Time `json:"final_payment_date"` + FailedPaymentCount int `json:"failed_payment_count,string"` + } + // Amount struct Amount struct { Currency string `json:"currency"` @@ -93,6 +108,26 @@ 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"` + } + + // BillingPlan struct + BillingPlan struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` + PaymentDefinitions []PaymentDefinition `json:"payment_definitions,omitempty"` + MerchantPreferences *MerchantPreferences `json:"merchant_preferences,omitempty"` + } + // Capture struct Capture struct { Amount *Amount `json:"amount,omitempty"` @@ -105,14 +140,20 @@ type ( Links []Link `json:"links,omitempty"` } + // ChargeModel struct + ChargeModel struct { + Type string `json:"type,omitempty"` + Amount AmountPayout `json:"amount,omitempty"` + } + // Client represents a Paypal REST API Client Client struct { - Client *http.Client - ClientID string - Secret string - APIBase string - Log io.Writer // If user set log file name all requests will be logged there - Token *TokenResponse + Client *http.Client + ClientID string + Secret string + APIBase string + Log io.Writer // If user set log file name all requests will be logged there + Token *TokenResponse tokenExpiresAt time.Time } @@ -179,6 +220,19 @@ type ( Details string `json:"details"` } + // ExecuteAgreementResponse struct + ExecuteAgreementResponse struct { + ID string `json:"id"` + State string `json:"state"` + Description string `json:"description,omitempty"` + Payer Payer `json:"payer"` + Plan BillingPlan `json:"plan"` + StartDate time.Time `json:"start_date"` + ShippingAddress ShippingAddress `json:"shipping_address"` + AgreementDetails AgreementDetails `json:"agreement_details"` + Links []Link `json:"links"` + } + // ExecuteResponse struct ExecuteResponse struct { ID string `json:"id"` @@ -218,6 +272,16 @@ type ( Enctype string `json:"enctype,omitempty"` } + // MerchantPreferences struct + MerchantPreferences struct { + SetupFee *AmountPayout `json:"setup_fee,omitempty"` + ReturnUrl string `json:"return_url,omitempty"` + CancelUrl string `json:"cancel_url,omitempty"` + AutoBillAmount string `json:"auto_bill_amount,omitempty"` + InitialFailAmountAction string `json:"initial_fail_amount_action,omitempty"` + MaxFailAttempts string `json:"max_fail_attempts,omitempty"` + } + // Order struct Order struct { ID string `json:"id,omitempty"` @@ -263,6 +327,18 @@ type ( ExperienceProfileID string `json:"experience_profile_id,omitempty"` } + // PaymentDefinition struct + PaymentDefinition struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Frequency string `json:"frequency,omitempty"` + FrequencyInterval string `json:"frequency_interval,omitempty"` + Amount AmountPayout `json:"amount,omitempty"` + Cycles string `json:"cycles,omitempty"` + ChargeModels []ChargeModel `json:"charge_models,omitempty"` + } + // PaymentResponse structure PaymentResponse struct { ID string `json:"id"` @@ -449,3 +525,8 @@ type ( func (r *ErrorResponse) Error() string { return fmt.Sprintf("%v %v: %d %s", r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message) } + +func (t JsonTime) MarshalJSON() ([]byte, error) { + stamp := fmt.Sprintf(`"%s"`, time.Time(t).UTC().Format(time.RFC3339)) + return []byte(stamp), nil +}