From 7fc697b711810e70df47480149bca8032f75ec2d Mon Sep 17 00:00:00 2001 From: UsherFall <98513748+UsherFall@users.noreply.github.com> Date: Fri, 26 May 2023 21:26:41 +0800 Subject: [PATCH] ci: fix bug in WeChat payment provider --- controllers/payment.go | 19 ++++----- idp/casdoor.go | 3 +- object/payment.go | 44 ++++++++++---------- object/product_test.go | 2 +- object/provider.go | 2 +- pp/alipay.go | 8 ++++ pp/gc.go | 8 ++++ pp/provider.go | 11 ++--- pp/util.go | 12 ++++++ pp/wechatpay.go | 83 +++++++++++++++++++++++++------------ web/src/ProviderEditPage.js | 2 +- 11 files changed, 126 insertions(+), 68 deletions(-) diff --git a/controllers/payment.go b/controllers/payment.go index 5391faab..be04e220 100644 --- a/controllers/payment.go +++ b/controllers/payment.go @@ -16,7 +16,6 @@ package controllers import ( "encoding/json" - "fmt" "github.com/beego/beego/utils/pagination" "github.com/casdoor/casdoor/object" @@ -156,15 +155,15 @@ func (c *ApiController) NotifyPayment() { body := c.Ctx.Input.RequestBody - ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName) - if ok { - _, err := c.Ctx.ResponseWriter.Write([]byte("success")) - if err != nil { - c.ResponseError(err.Error()) - return - } - } else { - panic(fmt.Errorf("NotifyPayment() failed: %v", ok)) + err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName) + + _, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse)) + if err2 != nil { + panic(err2) + } + + if err != nil { + panic(err) } } diff --git a/idp/casdoor.go b/idp/casdoor.go index 67b810cf..3ee35405 100644 --- a/idp/casdoor.go +++ b/idp/casdoor.go @@ -16,6 +16,7 @@ package idp import ( "encoding/json" + "errors" "fmt" "io" "net/http" @@ -83,7 +84,7 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) { // check if token is expired if pToken.ExpiresIn <= 0 { - return nil, fmt.Errorf("%s", pToken.AccessToken) + return nil, errors.New(pToken.AccessToken) } token := &oauth2.Token{ AccessToken: pToken.AccessToken, diff --git a/object/payment.go b/object/payment.go index b0aec636..4f3f7d78 100644 --- a/object/payment.go +++ b/object/payment.go @@ -152,46 +152,47 @@ func DeletePayment(payment *Payment) bool { return affected != 0 } -func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) { +func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error, string) { + provider := getProvider(owner, providerName) + + pProvider, cert, err := provider.getPaymentProvider() + if err != nil { + panic(err) + } + payment := getPayment(owner, paymentName) if payment == nil { - return nil, fmt.Errorf("the payment: %s does not exist", paymentName) + err = fmt.Errorf("the payment: %s does not exist", paymentName) + return nil, err, pProvider.GetResponseError(err) } product := getProduct(owner, productName) if product == nil { - return nil, fmt.Errorf("the product: %s does not exist", productName) - } - - provider, err := product.getProvider(providerName) - if err != nil { - return payment, err - } - - pProvider, cert, err := provider.getPaymentProvider() - if err != nil { - return payment, err + err = fmt.Errorf("the product: %s does not exist", productName) + return payment, err, pProvider.GetResponseError(err) } productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey) if err != nil { - return payment, err + return payment, err, pProvider.GetResponseError(err) } if productDisplayName != "" && productDisplayName != product.DisplayName { - return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName) + err = fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName) + return payment, err, pProvider.GetResponseError(err) } if price != product.Price { - return nil, fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price) + err = fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price) + return payment, err, pProvider.GetResponseError(err) } - return payment, nil + err = nil + return payment, err, pProvider.GetResponseError(err) } -func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool { - payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName) - +func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (error, string) { + payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName) if payment != nil { if err != nil { payment.State = "Error" @@ -203,8 +204,7 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam UpdatePayment(payment.GetId(), payment) } - ok := err == nil - return ok + return err, errorResponse } func invoicePayment(payment *Payment) (string, error) { diff --git a/object/product_test.go b/object/product_test.go index 682777ca..4ac9478f 100644 --- a/object/product_test.go +++ b/object/product_test.go @@ -30,7 +30,7 @@ func TestProduct(t *testing.T) { 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) + pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, provider.ClientSecret2, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2) if err != nil { panic(err) } diff --git a/object/provider.go b/object/provider.go index 9382687d..97548164 100644 --- a/object/provider.go +++ b/object/provider.go @@ -274,7 +274,7 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) { } } - pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2) + pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, p.ClientSecret2, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2) if err != nil { return nil, cert, err } diff --git a/pp/alipay.go b/pp/alipay.go index 9a54b0c7..21b453e2 100644 --- a/pp/alipay.go +++ b/pp/alipay.go @@ -94,3 +94,11 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) { return "", nil } + +func (pp *AlipayPaymentProvider) GetResponseError(err error) string { + if err == nil { + return "success" + } else { + return "fail" + } +} diff --git a/pp/gc.go b/pp/gc.go index 78fd48e9..06929387 100644 --- a/pp/gc.go +++ b/pp/gc.go @@ -329,3 +329,11 @@ func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, p return invoiceRespInfo.Url, nil } + +func (pp *GcPaymentProvider) GetResponseError(err error) string { + if err == nil { + return "success" + } else { + return "fail" + } +} diff --git a/pp/provider.go b/pp/provider.go index a7f27794..4d9fe930 100644 --- a/pp/provider.go +++ b/pp/provider.go @@ -20,20 +20,21 @@ type PaymentProvider interface { Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) Notify(request *http.Request, body []byte, authorityPublicKey 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) + GetResponseError(err error) string } -func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) { +func GetPaymentProvider(typ string, clientId string, clientSecret string, host string, appCertificate string, certSerialNo string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) { if typ == "Alipay" { - newAlipayPaymentProvider, err := NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey) + newAlipayPaymentProvider, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey) if err != nil { return nil, err } return newAlipayPaymentProvider, nil } else if typ == "GC" { - return NewGcPaymentProvider(appId, clientSecret, host), nil + return NewGcPaymentProvider(clientId, clientSecret, host), nil } else if typ == "WeChat Pay" { - // appId, mchId, mchCertSerialNumber, apiV3Key, privateKey - newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, appCertificate, clientSecret, appPrivateKey) + // appId, mchId, mchCert, mchCertSerialNumber, apiV3Key, privateKey + newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, clientId, appCertificate, certSerialNo, clientSecret, appPrivateKey) if err != nil { return nil, err } diff --git a/pp/util.go b/pp/util.go index d7123f26..26eead1c 100644 --- a/pp/util.go +++ b/pp/util.go @@ -23,3 +23,15 @@ func getPriceString(price float64) string { priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".") return priceString } + +func joinAttachString(tokens []string) string { + return strings.Join(tokens, "|") +} + +func parseAttachString(s string) (string, string, string, error) { + tokens := strings.Split(s, "|") + if len(tokens) != 3 { + return "", "", "", fmt.Errorf("parseAttachString() error: len(tokens) expected 3, got: %d", len(tokens)) + } + return tokens[0], tokens[1], tokens[2], nil +} diff --git a/pp/wechatpay.go b/pp/wechatpay.go index 083efa25..315f288b 100644 --- a/pp/wechatpay.go +++ b/pp/wechatpay.go @@ -16,7 +16,7 @@ package pp import ( "context" - "fmt" + "errors" "net/http" "github.com/casdoor/casdoor/util" @@ -24,12 +24,21 @@ import ( "github.com/go-pay/gopay/wechat/v3" ) +type WechatPayNotifyResponse struct { + Code string `json:"Code"` + Message string `json:"Message"` +} + type WechatPaymentProvider struct { ClientV3 *wechat.ClientV3 appId string } -func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber string, apiV3Key string, privateKey string) (*WechatPaymentProvider, error) { +func NewWechatPaymentProvider(appId string, mchId string, cert string, mchCertSerialNumber string, apiV3Key string, privateKey string) (*WechatPaymentProvider, error) { + if appId == "" && mchId == "" && cert == "" && mchCertSerialNumber == "" && apiV3Key == "" && privateKey == "" { + return &WechatPaymentProvider{}, nil + } + pp := &WechatPaymentProvider{appId: appId} clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey) @@ -37,11 +46,13 @@ func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber st return nil, err } - err = clientV3.AutoVerifySign() + platformCert, serialNo, err := clientV3.GetAndSelectNewestCert() if err != nil { return nil, err } - pp.ClientV3 = clientV3 + + pp.ClientV3 = clientV3.SetPlatformCert([]byte(platformCert), serialNo) + return pp, nil } @@ -50,53 +61,71 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa bm := gopay.BodyMap{} - bm.Set("providerName", providerName) - bm.Set("productName", productName) - - bm.Set("return_url", returnUrl) + bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName})) + bm.Set("appid", pp.appId) + bm.Set("description", productDisplayName) bm.Set("notify_url", notifyUrl) - - bm.Set("body", productDisplayName) bm.Set("out_trade_no", paymentName) - bm.Set("total_fee", getPriceString(price)) + bm.SetBodyMap("amount", func(bm gopay.BodyMap) { + bm.Set("total", int(price*100)) + bm.Set("currency", "CNY") + }) - wechatRsp, err := pp.ClientV3.V3TransactionJsapi(context.Background(), bm) + wxRsp, err := pp.ClientV3.V3TransactionNative(context.Background(), bm) if err != nil { return "", err } - payUrl := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect", pp.appId, wechatRsp.Response.PrepayId) - return payUrl, nil + if wxRsp.Code != wechat.Success { + return "", errors.New(wxRsp.Error) + } + + return wxRsp.Response.CodeUrl, nil } func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { - bm, err := wechat.V3ParseNotifyToBodyMap(request) - if err != nil { - return "", "", 0, "", "", err - } - - providerName := bm.Get("providerName") - productName := bm.Get("productName") - - productDisplayName := bm.Get("body") - paymentName := bm.Get("out_trade_no") - price := util.ParseFloat(bm.Get("total_fee")) - notifyReq, err := wechat.V3ParseNotify(request) if err != nil { panic(err) } cert := pp.ClientV3.WxPublicKey() - err = notifyReq.VerifySignByPK(cert) if err != nil { return "", "", 0, "", "", err } + apiKey := string(pp.ClientV3.ApiV3Key) + result, err := notifyReq.DecryptCipherText(apiKey) + if err != nil { + return "", "", 0, "", "", err + } + + paymentName := result.OutTradeNo + price := float64(result.Amount.PayerTotal) / 100 + + productDisplayName, productName, providerName, err := parseAttachString(result.Attach) + if err != nil { + return "", "", 0, "", "", err + } + return productDisplayName, paymentName, price, productName, providerName, nil } func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) { return "", nil } + +func (pp *WechatPaymentProvider) GetResponseError(err error) string { + response := &WechatPayNotifyResponse{ + Code: "SUCCESS", + Message: "", + } + + if err != nil { + response.Code = "FAIL" + response.Message = err.Error() + } + + return util.StructToJson(response) +} diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index e6beee82..50dd21ff 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -856,7 +856,7 @@ class ProviderEditPage extends React.Component { this.state.provider.type === "WeChat Pay" ? ( - {Setting.getLabel("cert", "cert")} : + {Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} : {