fix: fix bug in PayPal payment provider (#1959)

This commit is contained in:
UsherFall
2023-06-12 13:43:37 +08:00
committed by GitHub
parent 0e14a2597e
commit 451fc9034f
11 changed files with 96 additions and 81 deletions

View File

@ -178,10 +178,11 @@ func (c *ApiController) NotifyPayment() {
providerName := c.Ctx.Input.Param(":provider") providerName := c.Ctx.Input.Param(":provider")
productName := c.Ctx.Input.Param(":product") productName := c.Ctx.Input.Param(":product")
paymentName := c.Ctx.Input.Param(":payment") paymentName := c.Ctx.Input.Param(":payment")
orderId := c.Ctx.Input.Param("order")
body := c.Ctx.Input.RequestBody body := c.Ctx.Input.RequestBody
err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName) err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName, orderId)
_, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse)) _, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse))
if err2 != nil { if err2 != nil {

View File

@ -180,11 +180,11 @@ func (c *ApiController) BuyProduct() {
return return
} }
payUrl, err := object.BuyProduct(id, providerName, user, host) payUrl, orderId, err := object.BuyProduct(id, providerName, user, host)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
c.ResponseOk(payUrl) c.ResponseOk(payUrl, orderId)
} }

View File

@ -149,7 +149,7 @@ func DeletePayment(payment *Payment) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error, string) { func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (*Payment, error, string) {
provider, err := getProvider(owner, providerName) provider, err := getProvider(owner, providerName)
if err != nil { if err != nil {
panic(err) panic(err)
@ -180,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam
return payment, err, pProvider.GetResponseError(err) return payment, err, pProvider.GetResponseError(err)
} }
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey) productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
if err != nil { if err != nil {
return payment, err, pProvider.GetResponseError(err) return payment, err, pProvider.GetResponseError(err)
} }
@ -199,8 +199,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam
return payment, err, pProvider.GetResponseError(err) return payment, err, pProvider.GetResponseError(err)
} }
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (error, string) { func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (error, string) {
payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName) payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName, orderId)
if payment != nil { if payment != nil {
if err != nil { if err != nil {
payment.State = "Error" payment.State = "Error"

View File

@ -156,24 +156,24 @@ func (product *Product) getProvider(providerId string) (*Provider, error) {
return provider, nil return provider, nil
} }
func BuyProduct(id string, providerName string, user *User, host string) (string, error) { func BuyProduct(id string, providerName string, user *User, host string) (string, string, error) {
product, err := GetProduct(id) product, err := GetProduct(id)
if err != nil { if err != nil {
return "", err return "", "", err
} }
if product == nil { if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id) return "", "", fmt.Errorf("the product: %s does not exist", id)
} }
provider, err := product.getProvider(providerName) provider, err := product.getProvider(providerName)
if err != nil { if err != nil {
return "", err return "", "", err
} }
pProvider, _, err := provider.getPaymentProvider() pProvider, _, err := provider.getPaymentProvider()
if err != nil { if err != nil {
return "", err return "", "", err
} }
owner := product.Owner owner := product.Owner
@ -186,9 +186,9 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName) returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName) notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl) payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
if err != nil { if err != nil {
return "", err return "", "", err
} }
payment := Payment{ payment := Payment{
@ -217,14 +217,14 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
affected, err := AddPayment(&payment) affected, err := AddPayment(&payment)
if err != nil { if err != nil {
return "", err return "", "", err
} }
if !affected { if !affected {
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
} }
return payUrl, err return payUrl, orderId, err
} }
func ExtendProductWithProviders(product *Product) error { func ExtendProductWithProviders(product *Product) error {

View File

@ -38,7 +38,7 @@ func TestProduct(t *testing.T) {
paymentName := util.GenerateTimeId() paymentName := util.GenerateTimeId()
returnUrl := "" returnUrl := ""
notifyUrl := "" notifyUrl := ""
payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl) payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
return pp, nil return pp, nil
} }
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn // pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
@ -62,12 +62,12 @@ func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, pa
payUrl, err := pp.Client.TradePagePay(context.Background(), bm) payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return payUrl, nil return payUrl, "", nil
} }
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
bm, err := alipay.ParseNotifyToBodyMap(request) bm, err := alipay.ParseNotifyToBodyMap(request)
if err != nil { if err != nil {
return "", "", 0, "", "", err return "", "", 0, "", "", err

View File

@ -26,12 +26,12 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
return pp, nil return pp, nil
} }
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
payUrl := fmt.Sprintf("/payments/%s/result", paymentName) payUrl := fmt.Sprintf("/payments/%s/result", paymentName)
return payUrl, nil return payUrl, "", nil
} }
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
return "", "", 0, "", "", nil return "", "", 0, "", "", nil
} }

View File

@ -153,7 +153,7 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
return respBytes, nil return respBytes, nil
} }
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
payReqInfo := GcPayReqInfo{ payReqInfo := GcPayReqInfo{
OrderDate: util.GenerateSimpleTimeId(), OrderDate: util.GenerateSimpleTimeId(),
OrderNo: paymentName, OrderNo: paymentName,
@ -168,7 +168,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
b, err := json.Marshal(payReqInfo) b, err := json.Marshal(payReqInfo)
if err != nil { if err != nil {
return "", err return "", "", err
} }
body := GcRequestBody{ body := GcRequestBody{
@ -184,39 +184,39 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
bodyBytes, err := json.Marshal(body) bodyBytes, err := json.Marshal(body)
if err != nil { if err != nil {
return "", err return "", "", err
} }
respBytes, err := pp.doPost(bodyBytes) respBytes, err := pp.doPost(bodyBytes)
if err != nil { if err != nil {
return "", err return "", "", err
} }
var respBody GcResponseBody var respBody GcResponseBody
err = json.Unmarshal(respBytes, &respBody) err = json.Unmarshal(respBytes, &respBody)
if err != nil { if err != nil {
return "", err return "", "", err
} }
if respBody.ReturnCode != "SUCCESS" { if respBody.ReturnCode != "SUCCESS" {
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg) return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
} }
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data) payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
if err != nil { if err != nil {
return "", err return "", "", err
} }
var payRespInfo GcPayRespInfo var payRespInfo GcPayRespInfo
err = json.Unmarshal(payRespInfoBytes, &payRespInfo) err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return payRespInfo.PayUrl, nil return payRespInfo.PayUrl, "", nil
} }
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
reqBody := GcRequestBody{} reqBody := GcRequestBody{}
m, err := url.ParseQuery(string(body)) m, err := url.ParseQuery(string(body))
if err != nil { if err != nil {

View File

@ -16,10 +16,13 @@ package pp
import ( import (
"context" "context"
"fmt" "errors"
"net/http" "net/http"
"strconv"
"github.com/plutov/paypal/v4" "github.com/go-pay/gopay"
"github.com/go-pay/gopay/paypal"
"github.com/go-pay/gopay/pkg/util"
) )
type PaypalPaymentProvider struct { type PaypalPaymentProvider struct {
@ -29,7 +32,7 @@ type PaypalPaymentProvider struct {
func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentProvider, error) { func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentProvider, error) {
pp := &PaypalPaymentProvider{} pp := &PaypalPaymentProvider{}
client, err := paypal.NewClient(clientID, secret, paypal.APIBaseSandBox) client, err := paypal.NewClient(clientID, secret, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -38,51 +41,62 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
return pp, nil return pp, nil
} }
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.SetLog(os.Stdout) // Set log to terminal stdout // pp.Client.DebugSwitch = gopay.DebugOn // Set log to terminal stdout
receiverEmail := "sb-tmsqa26118644@business.example.com" priceStr := strconv.FormatFloat(price, 'f', 2, 64)
var pus []*paypal.PurchaseUnit
amount := paypal.AmountPayout{ item := &paypal.PurchaseUnit{
Value: fmt.Sprintf("%.2f", price), ReferenceId: util.GetRandomString(16),
Currency: "USD", Amount: &paypal.Amount{
} CurrencyCode: currency,
Value: priceStr,
description := fmt.Sprintf("%s-%s", providerName, productName)
payout := paypal.Payout{
SenderBatchHeader: &paypal.SenderBatchHeader{
EmailSubject: description,
},
Items: []paypal.PayoutItem{
{
RecipientType: "EMAIL",
Receiver: receiverEmail,
Amount: &amount,
Note: description,
SenderItemID: description,
},
}, },
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
} }
pus = append(pus, item)
_, err := pp.Client.GetAccessToken(context.Background()) bm := make(gopay.BodyMap)
bm.Set("intent", "CAPTURE")
bm.Set("purchase_units", pus)
bm.SetBodyMap("payment_source", func(b1 gopay.BodyMap) {
b1.SetBodyMap("paypal", func(b2 gopay.BodyMap) {
b2.Set("brand_name", "Casdoor")
b2.Set("return_url", returnUrl)
})
})
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
if err != nil { if err != nil {
return "", err return "", "", err
}
if ppRsp.Code != paypal.Success {
return "", "", errors.New(ppRsp.Error)
} }
payoutResponse, err := pp.Client.CreatePayout(context.Background(), payout) return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
if err != nil {
return "", err
}
payUrl := payoutResponse.Links[0].Href
return payUrl, nil
} }
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
// The PayPal SDK does not directly support IPN verification. ppRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
// So, you need to implement this part according to PayPal's IPN guide. if err != nil {
return "", "", 0, "", "", nil return "", "", 0, "", "", err
}
if ppRsp.Code != paypal.Success {
return "", "", 0, "", "", errors.New(ppRsp.Error)
}
paymentName := ppRsp.Response.Id
price, err := strconv.ParseFloat(ppRsp.Response.PurchaseUnits[0].Amount.Value, 64)
if err != nil {
return "", "", 0, "", "", err
}
productDisplayName, productName, providerName, err := parseAttachString(ppRsp.Response.PurchaseUnits[0].Description)
if err != nil {
return "", "", 0, "", "", err
}
return productDisplayName, paymentName, price, productName, providerName, nil
} }
func (pp *PaypalPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) { func (pp *PaypalPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {

View File

@ -17,8 +17,8 @@ package pp
import "net/http" import "net/http"
type PaymentProvider interface { type PaymentProvider interface {
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error)
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
GetResponseError(err error) string GetResponseError(err error) string
} }

View File

@ -56,7 +56,7 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
return pp, nil return pp, nil
} }
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn // pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
@ -73,17 +73,17 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm) wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
if err != nil { if err != nil {
return "", err return "", "", err
} }
if wxRsp.Code != wechat.Success { if wxRsp.Code != wechat.Success {
return "", errors.New(wxRsp.Error) return "", "", errors.New(wxRsp.Error)
} }
return wxRsp.Response.CodeUrl, nil return wxRsp.Response.CodeUrl, "", nil
} }
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
notifyReq, err := wechat.V3ParseNotify(request) notifyReq, err := wechat.V3ParseNotify(request)
if err != nil { if err != nil {
panic(err) panic(err)