diff --git a/doc/paypal.md b/doc/paypal.md index 957a24d7..21556177 100644 --- a/doc/paypal.md +++ b/doc/paypal.md @@ -106,7 +106,7 @@ return ### PayPal API * AccessToken - * 获取AccessToken(Get AccessToken):`client.GetAccessToken()` + * 获取AccessToken(Get AccessToken):`client.GetAccessToken()` * 订单 * 创建订单(Create order):`client.CreateOrder()` * 订单详情(Show order details):`client.OrderDetail()` @@ -122,3 +122,5 @@ return * 支付捕获详情(Show captured payment details):`client.PaymentCaptureDetail()` * 支付捕获退款(Refund captured payment):`client.PaymentCaptureRefund()` * 支付退款详情(Show refund details):`client.PaymentRefundDetail()` +* 订阅 + * 创建订单(Create order):`client.CreateBillingPlan()` \ No newline at end of file diff --git a/paypal/constant.go b/paypal/constant.go index 527a8b83..dbdad1ae 100644 --- a/paypal/constant.go +++ b/paypal/constant.go @@ -30,4 +30,6 @@ const ( paymentCaptureRefund = "/v2/payments/captures/%s/refund" // capture_id 支付捕获退款 POST paymentRefundDetail = "/v2/payments/refunds/%s" // refund_id 支付退款详情 GET + // 订阅相关 + subscriptionCreate = "/v1/billing/plans" // 创建订阅 POST ) diff --git a/paypal/model.go b/paypal/model.go index 10f66b12..3d924f8d 100644 --- a/paypal/model.go +++ b/paypal/model.go @@ -373,3 +373,59 @@ type NetAmountBreakdown struct { ConvertedAmount *Amount `json:"converted_amount,omitempty"` ExchangeRate *ExchangeRate `json:"exchange_rate,omitempty"` } + +// Subscription Model + +type Frequency struct { + IntervalUnit string `json:"interval_unit"` + IntervalCount int `json:"interval_count"` +} + +type PricingScheme struct { + FixedPrice *FixedPrice `json:"fixed_price"` +} + +type FixedPrice struct { + Value string `json:"value"` + CurrencyCode string `json:"currency_code"` +} + +type BillingCycles struct { + Frequency *Frequency `json:"frequency"` + TenureType string `json:"tenure_type"` + Sequence int `json:"sequence"` + TotalCycles int `json:"total_cycles"` + PricingScheme *PricingScheme `json:"pricing_scheme"` +} + +type Plans struct { + ProductId string `json:"product_id"` + Name string `json:"name"` + Description string `json:"description"` + BillingCycles []*BillingCycles `json:"billing_cycles"` + PaymentDefinitions *PaymentPreferences `json:"payment_preferences"` +} + +type PaymentPreferences struct { + AutoBillOutstanding bool `json:"auto_bill_outstanding"` + SetupFeeFailureAction string `json:"setup_fee_failure_action"` + PaymentFailureThreshold int `json:"payment_failure_threshold"` +} + +type CreateBillingRsp struct { + Code int `json:"-"` + Error string `json:"-"` + ErrorResponse *ErrorResponse `json:"-"` + Response *BillingDetail `json:"response,omitempty"` +} + +type BillingDetail struct { + ID string `json:"id"` + ProductID string `json:"product_id"` + Name string `json:"name"` + Status string `json:"status"` + Description string `json:"description"` + UsageType string `json:"usage_type"` + CreateTime string `json:"create_time"` + Links []*Link `json:"links"` +} diff --git a/paypal/subscriptions.go b/paypal/subscriptions.go new file mode 100644 index 00000000..d852e7ad --- /dev/null +++ b/paypal/subscriptions.go @@ -0,0 +1,34 @@ +package paypal + +import ( + "context" + "encoding/json" + "fmt" + "github.com/go-pay/gopay" + "net/http" +) + +// 创建订阅计划(CreateBillingPlan) +// Code = 0 is success +// 文档:https://developer.paypal.com/docs/api/subscriptions/v1/#plans_create +func (c *Client) CreateBillingPlan(ctx context.Context, bm gopay.BodyMap) (ppRsp *CreateBillingRsp, err error) { + if err = bm.CheckEmptyError("product_id", "billing_cycles"); err != nil { + return nil, err + } + res, bs, err := c.doPayPalPost(ctx, bm, subscriptionCreate) + if err != nil { + return nil, err + } + ppRsp = &CreateBillingRsp{Code: Success} + ppRsp.Response = new(BillingDetail) + if err = json.Unmarshal(bs, ppRsp.Response); err != nil { + return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs)) + } + if res.StatusCode != http.StatusCreated { + ppRsp.Code = res.StatusCode + ppRsp.Error = string(bs) + ppRsp.ErrorResponse = new(ErrorResponse) + _ = json.Unmarshal(bs, ppRsp.ErrorResponse) + } + return ppRsp, nil +} diff --git a/paypal/subscriptions_test.go b/paypal/subscriptions_test.go new file mode 100644 index 00000000..f6ac01e2 --- /dev/null +++ b/paypal/subscriptions_test.go @@ -0,0 +1,57 @@ +package paypal + +import ( + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/pkg/xlog" + "testing" +) + +func TestClient_CreateBillingPlan(t *testing.T) { + var billingCycles []*BillingCycles + var billingCycle = &BillingCycles{ + Frequency: &Frequency{ + IntervalUnit: "MONTH", + IntervalCount: 1, + }, + TenureType: "REGULAR", + Sequence: 1, + TotalCycles: 0, + PricingScheme: &PricingScheme{ + FixedPrice: &FixedPrice{ + Value: "101", + CurrencyCode: "USD", + }, + }, + } + billingCycles = append(billingCycles, billingCycle) + + // 创建 PayPal 支付订单 + bm := make(gopay.BodyMap) + bm.Set("product_id", "PROD-9TH539347F0791830"). + Set("name", "gopay"). + Set("billing_cycles", billingCycles). + Set("description", "Monthly subscription for premium users"). + Set("payment_preferences", &PaymentPreferences{ + AutoBillOutstanding: true, + SetupFeeFailureAction: "CONTINUE", + PaymentFailureThreshold: 3, + }) + + xlog.Debug("bm:", bm.JsonBody()) + + ppRsp, err := client.CreateBillingPlan(ctx, bm) + if err != nil { + xlog.Error(err) + return + } + if ppRsp.Code != Success { + xlog.Debugf("ppRsp.Code: %+v", ppRsp.Code) + xlog.Debugf("ppRsp.Error: %+v", ppRsp.Error) + xlog.Debugf("ppRsp.ErrorResponse: %+v", ppRsp.ErrorResponse) + return + } + xlog.Debugf("ppRsp.Response: %+v", ppRsp.Response) + for _, v := range ppRsp.Response.Links { + xlog.Debugf("ppRsp.Response.Links: %+v", v) + } +}