ci: fix bug in WeChat payment provider

This commit is contained in:
UsherFall 2023-05-26 21:26:41 +08:00
parent 80e6e7f0a7
commit 7fc697b711
11 changed files with 126 additions and 68 deletions

View File

@ -16,7 +16,6 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@ -156,15 +155,15 @@ func (c *ApiController) NotifyPayment() {
body := c.Ctx.Input.RequestBody body := c.Ctx.Input.RequestBody
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName) err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
if ok {
_, err := c.Ctx.ResponseWriter.Write([]byte("success")) _, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse))
if err != nil { if err2 != nil {
c.ResponseError(err.Error()) panic(err2)
return }
}
} else { if err != nil {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok)) panic(err)
} }
} }

View File

@ -16,6 +16,7 @@ package idp
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -83,7 +84,7 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
// check if token is expired // check if token is expired
if pToken.ExpiresIn <= 0 { if pToken.ExpiresIn <= 0 {
return nil, fmt.Errorf("%s", pToken.AccessToken) return nil, errors.New(pToken.AccessToken)
} }
token := &oauth2.Token{ token := &oauth2.Token{
AccessToken: pToken.AccessToken, AccessToken: pToken.AccessToken,

View File

@ -152,46 +152,47 @@ func DeletePayment(payment *Payment) bool {
return affected != 0 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) payment := getPayment(owner, paymentName)
if payment == nil { 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) product := getProduct(owner, productName)
if product == nil { if product == nil {
return nil, fmt.Errorf("the product: %s does not exist", productName) err = fmt.Errorf("the product: %s does not exist", productName)
} return payment, err, pProvider.GetResponseError(err)
provider, err := product.getProvider(providerName)
if err != nil {
return payment, err
}
pProvider, cert, err := provider.getPaymentProvider()
if err != nil {
return payment, 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)
if err != nil { if err != nil {
return payment, err return payment, err, pProvider.GetResponseError(err)
} }
if productDisplayName != "" && productDisplayName != product.DisplayName { 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 { 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 { func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (error, string) {
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName) payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName)
if payment != nil { if payment != nil {
if err != nil { if err != nil {
payment.State = "Error" payment.State = "Error"
@ -203,8 +204,7 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam
UpdatePayment(payment.GetId(), payment) UpdatePayment(payment.GetId(), payment)
} }
ok := err == nil return err, errorResponse
return ok
} }
func invoicePayment(payment *Payment) (string, error) { func invoicePayment(payment *Payment) (string, error) {

View File

@ -30,7 +30,7 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123") product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay") provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-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 { if err != nil {
panic(err) panic(err)
} }

View File

@ -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 { if err != nil {
return nil, cert, err return nil, cert, err
} }

View File

@ -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) { func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
return "", nil return "", nil
} }
func (pp *AlipayPaymentProvider) GetResponseError(err error) string {
if err == nil {
return "success"
} else {
return "fail"
}
}

View File

@ -329,3 +329,11 @@ func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, p
return invoiceRespInfo.Url, nil return invoiceRespInfo.Url, nil
} }
func (pp *GcPaymentProvider) GetResponseError(err error) string {
if err == nil {
return "success"
} else {
return "fail"
}
}

View File

@ -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) 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) 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) 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" { if typ == "Alipay" {
newAlipayPaymentProvider, err := NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey) newAlipayPaymentProvider, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newAlipayPaymentProvider, nil return newAlipayPaymentProvider, nil
} else if typ == "GC" { } else if typ == "GC" {
return NewGcPaymentProvider(appId, clientSecret, host), nil return NewGcPaymentProvider(clientId, clientSecret, host), nil
} else if typ == "WeChat Pay" { } else if typ == "WeChat Pay" {
// appId, mchId, mchCertSerialNumber, apiV3Key, privateKey // appId, mchId, mchCert, mchCertSerialNumber, apiV3Key, privateKey
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, appCertificate, clientSecret, appPrivateKey) newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, clientId, appCertificate, certSerialNo, clientSecret, appPrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,3 +23,15 @@ func getPriceString(price float64) string {
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".") priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
return priceString 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
}

View File

@ -16,7 +16,7 @@ package pp
import ( import (
"context" "context"
"fmt" "errors"
"net/http" "net/http"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -24,12 +24,21 @@ import (
"github.com/go-pay/gopay/wechat/v3" "github.com/go-pay/gopay/wechat/v3"
) )
type WechatPayNotifyResponse struct {
Code string `json:"Code"`
Message string `json:"Message"`
}
type WechatPaymentProvider struct { type WechatPaymentProvider struct {
ClientV3 *wechat.ClientV3 ClientV3 *wechat.ClientV3
appId string 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} pp := &WechatPaymentProvider{appId: appId}
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey) clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
@ -37,11 +46,13 @@ func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber st
return nil, err return nil, err
} }
err = clientV3.AutoVerifySign() platformCert, serialNo, err := clientV3.GetAndSelectNewestCert()
if err != nil { if err != nil {
return nil, err return nil, err
} }
pp.ClientV3 = clientV3
pp.ClientV3 = clientV3.SetPlatformCert([]byte(platformCert), serialNo)
return pp, nil return pp, nil
} }
@ -50,53 +61,71 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
bm.Set("providerName", providerName) bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
bm.Set("productName", productName) bm.Set("appid", pp.appId)
bm.Set("description", productDisplayName)
bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl) bm.Set("notify_url", notifyUrl)
bm.Set("body", productDisplayName)
bm.Set("out_trade_no", paymentName) 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 { if err != nil {
return "", err 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) if wxRsp.Code != wechat.Success {
return payUrl, nil 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) { 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) notifyReq, err := wechat.V3ParseNotify(request)
if err != nil { if err != nil {
panic(err) panic(err)
} }
cert := pp.ClientV3.WxPublicKey() cert := pp.ClientV3.WxPublicKey()
err = notifyReq.VerifySignByPK(cert) err = notifyReq.VerifySignByPK(cert)
if err != nil { if err != nil {
return "", "", 0, "", "", err 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 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) { func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
return "", nil 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)
}

View File

@ -856,7 +856,7 @@ class ProviderEditPage extends React.Component {
this.state.provider.type === "WeChat Pay" ? ( this.state.provider.type === "WeChat Pay" ? (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel("cert", "cert")} : {Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.cert} onChange={e => { <Input value={this.state.provider.cert} onChange={e => {