From 08a00929743529ca6ec3802903c4ed8810664130 Mon Sep 17 00:00:00 2001 From: haiwu <54203997+Chinoholo0807@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:30:51 +0800 Subject: [PATCH] feat: fix alipay payment provider (#2330) * feat: support alipay payment provider * feat: update notify params * feat: update root cert * feat: update ProviderEditPage.js * feat: gofumpt --- controllers/payment.go | 2 +- object/cert.go | 6 +-- object/cert.go~ | Bin 0 -> 5585 bytes object/payment.go | 13 +++--- object/product.go | 2 +- object/product_test.go | 49 ++++++++++------------- object/provider.go | 65 ++++++++++++++++++++++++------ pp/alipay.go | 75 ++++++++++++++++++++--------------- pp/dummy.go | 4 +- pp/gc.go | 2 +- pp/paypal.go | 3 +- pp/provider.go | 42 +------------------- pp/stripe.go | 3 +- pp/util.go | 8 ++++ pp/wechatpay.go | 3 +- web/src/PaymentResultPage.js | 2 +- web/src/ProviderEditPage.js | 16 ++++++++ 17 files changed, 157 insertions(+), 138 deletions(-) create mode 100644 object/cert.go~ diff --git a/controllers/payment.go b/controllers/payment.go index 348ad3c8..0f871788 100644 --- a/controllers/payment.go +++ b/controllers/payment.go @@ -179,7 +179,7 @@ func (c *ApiController) NotifyPayment() { body := c.Ctx.Input.RequestBody - payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName) + payment, err := object.NotifyPayment(body, owner, paymentName) if err != nil { c.ResponseError(err.Error()) return diff --git a/object/cert.go b/object/cert.go index 4b7b001c..f2644c50 100644 --- a/object/cert.go +++ b/object/cert.go @@ -33,10 +33,8 @@ type Cert struct { BitSize int `json:"bitSize"` ExpireInYears int `json:"expireInYears"` - Certificate string `xorm:"mediumtext" json:"certificate"` - PrivateKey string `xorm:"mediumtext" json:"privateKey"` - AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"` - AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"` + Certificate string `xorm:"mediumtext" json:"certificate"` + PrivateKey string `xorm:"mediumtext" json:"privateKey"` } func GetMaskedCert(cert *Cert) *Cert { diff --git a/object/cert.go~ b/object/cert.go~ new file mode 100644 index 0000000000000000000000000000000000000000..c38896e81832cf082951dad6ccc4646f00fb120f GIT binary patch literal 5585 vcmeIu0Sy2E0K%a6Pi+o2h(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0~Z4W7100z literal 0 HcmV?d00001 diff --git a/object/payment.go b/object/payment.go index 9109db04..4e2def1b 100644 --- a/object/payment.go +++ b/object/payment.go @@ -16,7 +16,6 @@ package object import ( "fmt" - "net/http" "github.com/casdoor/casdoor/pp" @@ -153,7 +152,7 @@ func DeletePayment(payment *Payment) (bool, error) { return affected != 0, nil } -func notifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) { +func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) { payment, err := getPayment(owner, paymentName) if err != nil { return nil, nil, err @@ -167,7 +166,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName if err != nil { return nil, nil, err } - pProvider, cert, err := provider.getPaymentProvider() + pProvider, err := GetPaymentProvider(provider) if err != nil { return nil, nil, err } @@ -181,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName return nil, nil, err } - notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, payment.OutOrderId) + notifyResult, err := pProvider.Notify(body, payment.OutOrderId) if err != nil { return payment, nil, err } @@ -202,8 +201,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName return payment, notifyResult, nil } -func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, error) { - payment, notifyResult, err := notifyPayment(request, body, owner, paymentName) +func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, error) { + payment, notifyResult, err := notifyPayment(body, owner, paymentName) if payment != nil { if err != nil { payment.State = pp.PaymentStateError @@ -231,7 +230,7 @@ func invoicePayment(payment *Payment) (string, error) { return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider) } - pProvider, _, err := provider.getPaymentProvider() + pProvider, err := GetPaymentProvider(provider) if err != nil { return "", err } diff --git a/object/product.go b/object/product.go index c7ce1609..d6ac396a 100644 --- a/object/product.go +++ b/object/product.go @@ -172,7 +172,7 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host return nil, err } - pProvider, _, err := provider.getPaymentProvider() + pProvider, err := GetPaymentProvider(provider) if err != nil { return nil, err } diff --git a/object/product_test.go b/object/product_test.go index 5271a30e..ab1b2fa1 100644 --- a/object/product_test.go +++ b/object/product_test.go @@ -17,31 +17,24 @@ package object -import ( - "testing" - - "github.com/casdoor/casdoor/pp" - "github.com/casdoor/casdoor/util" -) - -func TestProduct(t *testing.T) { - InitConfig() - - product, _ := GetProduct("admin/product_123") - provider, _ := getProvider(product.Owner, "provider_pay_alipay") - cert, _ := getCert(product.Owner, "cert-pay-alipay") - pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2) - if err != nil { - panic(err) - } - - paymentName := util.GenerateTimeId() - returnUrl := "" - notifyUrl := "" - payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl) - if err != nil { - panic(err) - } - - println(payUrl) -} +//func TestProduct(t *testing.T) { +// InitConfig() +// +// product, _ := GetProduct("admin/product_123") +// provider, _ := getProvider(product.Owner, "provider_pay_alipay") +// cert, _ := getCert(product.Owner, "cert-pay-alipay") +// pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2) +// if err != nil { +// panic(err) +// } +// +// paymentName := util.GenerateTimeId() +// returnUrl := "" +// notifyUrl := "" +// payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl) +// if err != nil { +// panic(err) +// } +// +// println(payUrl) +//} diff --git a/object/provider.go b/object/provider.go index 5f4570ea..33f77db1 100644 --- a/object/provider.go +++ b/object/provider.go @@ -251,30 +251,69 @@ func DeleteProvider(provider *Provider) (bool, error) { return affected != 0, nil } -func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) { +func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) { cert := &Cert{} if p.Cert != "" { var err error - cert, err = getCert(p.Owner, p.Cert) + cert, err = GetCert(util.GetId(p.Owner, p.Cert)) if err != nil { - return nil, nil, err + return nil, err } if cert == nil { - return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert) + return nil, fmt.Errorf("the cert: %s does not exist", p.Cert) } } - - pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2) - if err != nil { - return nil, cert, err + typ := p.Type + if typ == "Dummy" { + pp, err := pp.NewDummyPaymentProvider() + if err != nil { + return nil, err + } + return pp, nil + } else if typ == "Alipay" { + if p.Metadata != "" { + // alipay provider store rootCert's name in metadata + rootCert, err := GetCert(util.GetId(p.Owner, p.Metadata)) + if err != nil { + return nil, err + } + if rootCert == nil { + return nil, fmt.Errorf("the cert: %s does not exist", p.Metadata) + } + pp, err := pp.NewAlipayPaymentProvider(p.ClientId, cert.Certificate, cert.PrivateKey, rootCert.Certificate, rootCert.PrivateKey) + if err != nil { + return nil, err + } + return pp, nil + } else { + return nil, fmt.Errorf("the metadata of alipay provider is empty") + } + } else if typ == "GC" { + return pp.NewGcPaymentProvider(p.ClientId, p.ClientSecret, p.Host), nil + } else if typ == "WeChat Pay" { + pp, err := pp.NewWechatPaymentProvider(p.ClientId, p.ClientSecret, p.ClientId2, cert.Certificate, cert.PrivateKey) + if err != nil { + return nil, err + } + return pp, nil + } else if typ == "PayPal" { + pp, err := pp.NewPaypalPaymentProvider(p.ClientId, p.ClientSecret) + if err != nil { + return nil, err + } + return pp, nil + } else if typ == "Stripe" { + pp, err := pp.NewStripePaymentProvider(p.ClientId, p.ClientSecret) + if err != nil { + return nil, err + } + return pp, nil + } else { + return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type) } - if pProvider == nil { - return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type) - } - - return pProvider, cert, nil + return nil, nil } func (p *Provider) GetId() string { diff --git a/pp/alipay.go b/pp/alipay.go index 42170616..283129a5 100644 --- a/pp/alipay.go +++ b/pp/alipay.go @@ -16,9 +16,9 @@ package pp import ( "context" - "net/http" + "encoding/json" + "fmt" - "github.com/casdoor/casdoor/util" "github.com/go-pay/gopay" "github.com/go-pay/gopay/alipay" ) @@ -28,6 +28,11 @@ type AlipayPaymentProvider struct { } func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) { + // clientId => appId + // cert.Certificate => appCertificate + // cert.PrivateKey => appPrivateKey + // rootCert.Certificate => authorityPublicKey + // rootCert.PrivateKey => authorityRootPublicKey pp := &AlipayPaymentProvider{} client, err := alipay.NewClient(appId, appPrivateKey, true) @@ -46,54 +51,60 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey 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 - bm := gopay.BodyMap{} - - bm.Set("providerName", providerName) - bm.Set("productName", productName) - - bm.Set("return_url", returnUrl) - bm.Set("notify_url", notifyUrl) - - bm.Set("subject", productDisplayName) + pp.Client.SetReturnUrl(returnUrl) + pp.Client.SetNotifyUrl(notifyUrl) + bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName})) bm.Set("out_trade_no", paymentName) - bm.Set("total_amount", getPriceString(price)) + bm.Set("total_amount", priceFloat64ToString(price)) payUrl, err := pp.Client.TradePagePay(context.Background(), bm) if err != nil { return "", "", err } - return payUrl, "", nil + return payUrl, paymentName, nil } -func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { - bm, err := alipay.ParseNotifyToBodyMap(request) +func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { + bm := gopay.BodyMap{} + bm.Set("out_trade_no", orderId) + aliRsp, err := pp.Client.TradeQuery(context.Background(), bm) + notifyResult := &NotifyResult{} if err != nil { + errRsp := &alipay.ErrorResponse{} + unmarshalErr := json.Unmarshal([]byte(err.Error()), errRsp) + if unmarshalErr != nil { + return nil, err + } + if errRsp.SubCode == "ACQ.TRADE_NOT_EXIST" { + notifyResult.PaymentStatus = PaymentStateCanceled + return notifyResult, nil + } return nil, err } - - providerName := bm.Get("providerName") - productName := bm.Get("productName") - - productDisplayName := bm.Get("subject") - paymentName := bm.Get("out_trade_no") - price := util.ParseFloat(bm.Get("total_amount")) - - ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm) - if err != nil { - return nil, err + switch aliRsp.Response.TradeStatus { + case "WAIT_BUYER_PAY": + notifyResult.PaymentStatus = PaymentStateCreated + return notifyResult, nil + case "TRADE_CLOSED": + notifyResult.PaymentStatus = PaymentStateTimeout + return notifyResult, nil + case "TRADE_SUCCESS": + // skip + default: + notifyResult.PaymentStatus = PaymentStateError + notifyResult.NotifyMessage = fmt.Sprintf("unexpected alipay trade state: %v", aliRsp.Response.TradeStatus) + return notifyResult, nil } - if !ok { - return nil, err - } - notifyResult := &NotifyResult{ + productDisplayName, productName, providerName, _ := parseAttachString(aliRsp.Response.Subject) + notifyResult = &NotifyResult{ ProductName: productName, ProductDisplayName: productDisplayName, ProviderName: providerName, OrderId: orderId, PaymentStatus: PaymentStatePaid, - Price: price, - PaymentName: paymentName, + Price: priceStringToFloat64(aliRsp.Response.TotalAmount), + PaymentName: orderId, } return notifyResult, nil } diff --git a/pp/dummy.go b/pp/dummy.go index 9a2524d7..041f616c 100644 --- a/pp/dummy.go +++ b/pp/dummy.go @@ -14,8 +14,6 @@ package pp -import "net/http" - type DummyPaymentProvider struct{} func NewDummyPaymentProvider() (*DummyPaymentProvider, error) { @@ -27,7 +25,7 @@ func (pp *DummyPaymentProvider) Pay(providerName string, productName string, pay return returnUrl, "", nil } -func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { +func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { return &NotifyResult{ PaymentStatus: PaymentStatePaid, }, nil diff --git a/pp/gc.go b/pp/gc.go index 1d26ec5e..eff18077 100644 --- a/pp/gc.go +++ b/pp/gc.go @@ -216,7 +216,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN return payRespInfo.PayUrl, "", nil } -func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { +func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { reqBody := GcRequestBody{} m, err := url.ParseQuery(string(body)) if err != nil { diff --git a/pp/paypal.go b/pp/paypal.go index d54c2fa2..b3b41f22 100644 --- a/pp/paypal.go +++ b/pp/paypal.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "net/http" "strconv" "github.com/casdoor/casdoor/conf" @@ -88,7 +87,7 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil } -func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { +func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { notifyResult := &NotifyResult{} captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil) if err != nil { diff --git a/pp/provider.go b/pp/provider.go index 024b7f5d..200bfe5f 100644 --- a/pp/provider.go +++ b/pp/provider.go @@ -14,8 +14,6 @@ package pp -import "net/http" - type PaymentState string const ( @@ -42,45 +40,7 @@ type NotifyResult struct { type PaymentProvider interface { 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, orderId string) (*NotifyResult, error) + Notify(body []byte, orderId string) (*NotifyResult, error) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetResponseError(err error) string } - -func GetPaymentProvider(typ string, clientId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) { - if typ == "Dummy" { - pp, err := NewDummyPaymentProvider() - if err != nil { - return nil, err - } - return pp, nil - } else if typ == "Alipay" { - pp, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey) - if err != nil { - return nil, err - } - return pp, nil - } else if typ == "GC" { - return NewGcPaymentProvider(clientId, clientSecret, host), nil - } else if typ == "WeChat Pay" { - pp, err := NewWechatPaymentProvider(clientId, clientSecret, clientId2, appCertificate, appPrivateKey) - if err != nil { - return nil, err - } - return pp, nil - } else if typ == "PayPal" { - pp, err := NewPaypalPaymentProvider(clientId, clientSecret) - if err != nil { - return nil, err - } - return pp, nil - } else if typ == "Stripe" { - pp, err := NewStripePaymentProvider(clientId, clientSecret) - if err != nil { - return nil, err - } - return pp, nil - } - - return nil, nil -} diff --git a/pp/stripe.go b/pp/stripe.go index 182950db..6acf73a5 100644 --- a/pp/stripe.go +++ b/pp/stripe.go @@ -16,7 +16,6 @@ package pp import ( "fmt" - "net/http" "time" "github.com/casdoor/casdoor/conf" @@ -94,7 +93,7 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa return sCheckout.URL, sCheckout.ID, nil } -func (pp *StripePaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { +func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { notifyResult := &NotifyResult{} sCheckout, err := stripeCheckout.Get(orderId, nil) if err != nil { diff --git a/pp/util.go b/pp/util.go index 25442538..2f05e368 100644 --- a/pp/util.go +++ b/pp/util.go @@ -49,3 +49,11 @@ func priceFloat64ToInt64(price float64) int64 { func priceFloat64ToString(price float64) string { return strconv.FormatFloat(price, 'f', 2, 64) } + +func priceStringToFloat64(price string) float64 { + f, err := strconv.ParseFloat(price, 64) + if err != nil { + panic(err) + } + return f +} diff --git a/pp/wechatpay.go b/pp/wechatpay.go index 959195cc..a8fcc1e9 100644 --- a/pp/wechatpay.go +++ b/pp/wechatpay.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "net/http" "github.com/casdoor/casdoor/util" "github.com/go-pay/gopay" @@ -87,7 +86,7 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status } -func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { +func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { notifyResult := &NotifyResult{} queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId) if err != nil { diff --git a/web/src/PaymentResultPage.js b/web/src/PaymentResultPage.js index 19bfb123..fd11aa69 100644 --- a/web/src/PaymentResultPage.js +++ b/web/src/PaymentResultPage.js @@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component { payment: payment, }); if (payment.state === "Created") { - if (["PayPal", "Stripe"].includes(payment.type)) { + if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) { this.setState({ timeout: setTimeout(async() => { await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName); diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 801daae6..700598b0 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -1111,6 +1111,22 @@ class ProviderEditPage extends React.Component { ) : null } + { + (this.state.provider.type === "Alipay") ? ( + + + {Setting.getLabel(i18next.t("general:Root Cert"), i18next.t("general:Root Cert - Tooltip"))} : + + + + + + ) : null + } { this.state.provider.type === "Web3Onboard" ? (