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
This commit is contained in:
haiwu 2023-09-13 17:30:51 +08:00 committed by GitHub
parent bb04b10e8b
commit 08a0092974
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 157 additions and 138 deletions

View File

@ -179,7 +179,7 @@ func (c *ApiController) NotifyPayment() {
body := c.Ctx.Input.RequestBody 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@ -33,10 +33,8 @@ type Cert struct {
BitSize int `json:"bitSize"` BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"` ExpireInYears int `json:"expireInYears"`
Certificate string `xorm:"mediumtext" json:"certificate"` Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"` PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
} }
func GetMaskedCert(cert *Cert) *Cert { func GetMaskedCert(cert *Cert) *Cert {

BIN
object/cert.go~ Normal file

Binary file not shown.

View File

@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"net/http"
"github.com/casdoor/casdoor/pp" "github.com/casdoor/casdoor/pp"
@ -153,7 +152,7 @@ func DeletePayment(payment *Payment) (bool, error) {
return affected != 0, nil 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) payment, err := getPayment(owner, paymentName)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -167,7 +166,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pProvider, cert, err := provider.getPaymentProvider() pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -181,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
return nil, nil, err return nil, nil, err
} }
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, payment.OutOrderId) notifyResult, err := pProvider.Notify(body, payment.OutOrderId)
if err != nil { if err != nil {
return payment, nil, err return payment, nil, err
} }
@ -202,8 +201,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
return payment, notifyResult, nil return payment, notifyResult, nil
} }
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, error) { func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, error) {
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName) payment, notifyResult, err := notifyPayment(body, owner, paymentName)
if payment != nil { if payment != nil {
if err != nil { if err != nil {
payment.State = pp.PaymentStateError 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) return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
} }
pProvider, _, err := provider.getPaymentProvider() pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -172,7 +172,7 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
return nil, err return nil, err
} }
pProvider, _, err := provider.getPaymentProvider() pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -17,31 +17,24 @@
package object package object
import ( //func TestProduct(t *testing.T) {
"testing" // InitConfig()
//
"github.com/casdoor/casdoor/pp" // product, _ := GetProduct("admin/product_123")
"github.com/casdoor/casdoor/util" // 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)
func TestProduct(t *testing.T) { // if err != nil {
InitConfig() // panic(err)
// }
product, _ := GetProduct("admin/product_123") //
provider, _ := getProvider(product.Owner, "provider_pay_alipay") // paymentName := util.GenerateTimeId()
cert, _ := getCert(product.Owner, "cert-pay-alipay") // returnUrl := ""
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2) // notifyUrl := ""
if err != nil { // payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
panic(err) // if err != nil {
} // panic(err)
// }
paymentName := util.GenerateTimeId() //
returnUrl := "" // println(payUrl)
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)
}

View File

@ -251,30 +251,69 @@ func DeleteProvider(provider *Provider) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) { func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
cert := &Cert{} cert := &Cert{}
if p.Cert != "" { if p.Cert != "" {
var err error var err error
cert, err = getCert(p.Owner, p.Cert) cert, err = GetCert(util.GetId(p.Owner, p.Cert))
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
if cert == nil { 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)
} }
} }
typ := p.Type
pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2) if typ == "Dummy" {
if err != nil { pp, err := pp.NewDummyPaymentProvider()
return nil, cert, err 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, nil
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
}
return pProvider, cert, nil
} }
func (p *Provider) GetId() string { func (p *Provider) GetId() string {

View File

@ -16,9 +16,9 @@ package pp
import ( import (
"context" "context"
"net/http" "encoding/json"
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay" "github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay" "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) { 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{} pp := &AlipayPaymentProvider{}
client, err := alipay.NewClient(appId, appPrivateKey, true) 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) { 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{}
pp.Client.SetReturnUrl(returnUrl)
bm.Set("providerName", providerName) pp.Client.SetNotifyUrl(notifyUrl)
bm.Set("productName", productName) bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl)
bm.Set("subject", productDisplayName)
bm.Set("out_trade_no", paymentName) 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) payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return payUrl, "", nil return payUrl, paymentName, nil
} }
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
bm, err := alipay.ParseNotifyToBodyMap(request) bm := gopay.BodyMap{}
bm.Set("out_trade_no", orderId)
aliRsp, err := pp.Client.TradeQuery(context.Background(), bm)
notifyResult := &NotifyResult{}
if err != nil { 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 return nil, err
} }
switch aliRsp.Response.TradeStatus {
providerName := bm.Get("providerName") case "WAIT_BUYER_PAY":
productName := bm.Get("productName") notifyResult.PaymentStatus = PaymentStateCreated
return notifyResult, nil
productDisplayName := bm.Get("subject") case "TRADE_CLOSED":
paymentName := bm.Get("out_trade_no") notifyResult.PaymentStatus = PaymentStateTimeout
price := util.ParseFloat(bm.Get("total_amount")) return notifyResult, nil
case "TRADE_SUCCESS":
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm) // skip
if err != nil { default:
return nil, err notifyResult.PaymentStatus = PaymentStateError
notifyResult.NotifyMessage = fmt.Sprintf("unexpected alipay trade state: %v", aliRsp.Response.TradeStatus)
return notifyResult, nil
} }
if !ok { productDisplayName, productName, providerName, _ := parseAttachString(aliRsp.Response.Subject)
return nil, err notifyResult = &NotifyResult{
}
notifyResult := &NotifyResult{
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
OrderId: orderId, OrderId: orderId,
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
Price: price, Price: priceStringToFloat64(aliRsp.Response.TotalAmount),
PaymentName: paymentName, PaymentName: orderId,
} }
return notifyResult, nil return notifyResult, nil
} }

View File

@ -14,8 +14,6 @@
package pp package pp
import "net/http"
type DummyPaymentProvider struct{} type DummyPaymentProvider struct{}
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) { func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
@ -27,7 +25,7 @@ func (pp *DummyPaymentProvider) Pay(providerName string, productName string, pay
return returnUrl, "", nil 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{ return &NotifyResult{
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
}, nil }, nil

View File

@ -216,7 +216,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
return payRespInfo.PayUrl, "", nil 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{} reqBody := GcRequestBody{}
m, err := url.ParseQuery(string(body)) m, err := url.ParseQuery(string(body))
if err != nil { if err != nil {

View File

@ -18,7 +18,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"github.com/casdoor/casdoor/conf" "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 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{} notifyResult := &NotifyResult{}
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil) captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
if err != nil { if err != nil {

View File

@ -14,8 +14,6 @@
package pp package pp
import "net/http"
type PaymentState string type PaymentState string
const ( const (
@ -42,45 +40,7 @@ type NotifyResult struct {
type PaymentProvider interface { 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) 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) 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
} }
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
}

View File

@ -16,7 +16,6 @@ package pp
import ( import (
"fmt" "fmt"
"net/http"
"time" "time"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@ -94,7 +93,7 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
return sCheckout.URL, sCheckout.ID, nil 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{} notifyResult := &NotifyResult{}
sCheckout, err := stripeCheckout.Get(orderId, nil) sCheckout, err := stripeCheckout.Get(orderId, nil)
if err != nil { if err != nil {

View File

@ -49,3 +49,11 @@ func priceFloat64ToInt64(price float64) int64 {
func priceFloat64ToString(price float64) string { func priceFloat64ToString(price float64) string {
return strconv.FormatFloat(price, 'f', 2, 64) 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
}

View File

@ -18,7 +18,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay" "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 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{} notifyResult := &NotifyResult{}
queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId) queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId)
if err != nil { if err != nil {

View File

@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
payment: payment, payment: payment,
}); });
if (payment.state === "Created") { if (payment.state === "Created") {
if (["PayPal", "Stripe"].includes(payment.type)) { if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
this.setState({ this.setState({
timeout: setTimeout(async() => { timeout: setTimeout(async() => {
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName); await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);

View File

@ -1111,6 +1111,22 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
) : null ) : null
} }
{
(this.state.provider.type === "Alipay") ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Root Cert"), i18next.t("general:Root Cert - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.metadata} onChange={(value => {this.updateProviderField("metadata", value);})}>
{
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
}
</Select>
</Col>
</Row>
) : null
}
{ {
this.state.provider.type === "Web3Onboard" ? ( this.state.provider.type === "Web3Onboard" ? (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >