mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-02 03:00:18 +08:00
fix: fix paypal payment provider and refactor payment code (#2159)
* feat: support paypal payment provider * feat: support paypal flow * feat: use owner replace org for payment * feat: update paypal logic * feat: gofumpt * feat: update payment * fix: fix notify * feat: delete log
This commit is contained in:
21
pp/alipay.go
21
pp/alipay.go
@ -16,7 +16,6 @@ package pp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -67,10 +66,10 @@ func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, pa
|
||||
return payUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
bm, err := alipay.ParseNotifyToBodyMap(request)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providerName := bm.Get("providerName")
|
||||
@ -82,13 +81,21 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
|
||||
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return "", "", 0, "", "", fmt.Errorf("VerifySignWithCert() failed: %v", ok)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
notifyResult := &NotifyResult{
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OutOrderId: orderId,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
Price: price,
|
||||
PaymentName: paymentName,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
|
@ -31,8 +31,10 @@ func (pp *DummyPaymentProvider) Pay(providerName string, productName string, pay
|
||||
return payUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
return "", "", 0, "", "", nil
|
||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
return &NotifyResult{
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
|
22
pp/gc.go
22
pp/gc.go
@ -216,11 +216,11 @@ 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) (string, string, float64, string, string, error) {
|
||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
reqBody := GcRequestBody{}
|
||||
m, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqBody.Op = m["op"][0]
|
||||
@ -232,13 +232,13 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
|
||||
|
||||
notifyReqInfoBytes, err := base64.StdEncoding.DecodeString(reqBody.Data)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var notifyRespInfo GcNotifyRespInfo
|
||||
err = json.Unmarshal(notifyReqInfoBytes, ¬ifyRespInfo)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providerName := ""
|
||||
@ -249,10 +249,18 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
|
||||
price := notifyRespInfo.Amount
|
||||
|
||||
if notifyRespInfo.OrderState != "1" {
|
||||
return "", "", 0, "", "", fmt.Errorf("error order state: %s", notifyRespInfo.OrderDate)
|
||||
return nil, fmt.Errorf("error order state: %s", notifyRespInfo.OrderDate)
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
notifyResult := &NotifyResult{
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OutOrderId: orderId,
|
||||
Price: price,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
PaymentName: paymentName,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
|
88
pp/paypal.go
88
pp/paypal.go
@ -20,6 +20,8 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/paypal"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
@ -31,8 +33,14 @@ type PaypalPaymentProvider struct {
|
||||
|
||||
func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentProvider, error) {
|
||||
pp := &PaypalPaymentProvider{}
|
||||
|
||||
client, err := paypal.NewClient(clientID, secret, false)
|
||||
isProd := false
|
||||
if conf.GetConfigString("runmode") == "prod" {
|
||||
isProd = true
|
||||
}
|
||||
client, err := paypal.NewClient(clientID, secret, isProd)
|
||||
//if !isProd {
|
||||
// client.DebugSwitch = gopay.DebugOn
|
||||
//}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -42,27 +50,27 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
|
||||
}
|
||||
|
||||
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.DebugSwitch = gopay.DebugOn // Set log to terminal stdout
|
||||
|
||||
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md
|
||||
priceStr := strconv.FormatFloat(price, 'f', 2, 64)
|
||||
var pus []*paypal.PurchaseUnit
|
||||
item := &paypal.PurchaseUnit{
|
||||
units := make([]*paypal.PurchaseUnit, 0, 1)
|
||||
unit := &paypal.PurchaseUnit{
|
||||
ReferenceId: util.GetRandomString(16),
|
||||
Amount: &paypal.Amount{
|
||||
CurrencyCode: currency,
|
||||
Value: priceStr,
|
||||
CurrencyCode: currency, // e.g."USD"
|
||||
Value: priceStr, // e.g."100.00"
|
||||
},
|
||||
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
|
||||
}
|
||||
pus = append(pus, item)
|
||||
units = append(units, unit)
|
||||
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("intent", "CAPTURE")
|
||||
bm.Set("purchase_units", pus)
|
||||
bm.Set("purchase_units", units)
|
||||
bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
|
||||
b.Set("brand_name", "Casdoor")
|
||||
b.Set("locale", "en-PT")
|
||||
b.Set("return_url", returnUrl)
|
||||
b.Set("cancel_url", returnUrl)
|
||||
})
|
||||
|
||||
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
|
||||
@ -72,31 +80,65 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
|
||||
if ppRsp.Code != paypal.Success {
|
||||
return "", "", errors.New(ppRsp.Error)
|
||||
}
|
||||
|
||||
// {"id":"9BR68863NE220374S","status":"CREATED",
|
||||
// "links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"self","method":"GET"},
|
||||
// {"href":"https://www.sandbox.paypal.com/checkoutnow?token=9BR68863NE220374S","rel":"approve","method":"GET"},
|
||||
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"update","method":"PATCH"},
|
||||
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S/capture","rel":"capture","method":"POST"}]}
|
||||
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
|
||||
}
|
||||
|
||||
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
ppRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
|
||||
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
if ppRsp.Code != paypal.Success {
|
||||
return "", "", 0, "", "", errors.New(ppRsp.Error)
|
||||
if captureRsp.Code != paypal.Success {
|
||||
// If order is already captured, just skip this type of error and check the order detail
|
||||
if !(len(captureRsp.ErrorResponse.Details) == 1 && captureRsp.ErrorResponse.Details[0].Issue == "ORDER_ALREADY_CAPTURED") {
|
||||
return nil, errors.New(captureRsp.ErrorResponse.Message)
|
||||
}
|
||||
}
|
||||
// Check the order detail
|
||||
detailRsp, err := pp.Client.OrderDetail(context.Background(), orderId, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if captureRsp.Code != paypal.Success {
|
||||
return nil, errors.New(captureRsp.ErrorResponse.Message)
|
||||
}
|
||||
|
||||
paymentName := ppRsp.Response.Id
|
||||
price, err := strconv.ParseFloat(ppRsp.Response.PurchaseUnits[0].Amount.Value, 64)
|
||||
paymentName := detailRsp.Response.Id
|
||||
price, err := strconv.ParseFloat(detailRsp.Response.PurchaseUnits[0].Amount.Value, 64)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
productDisplayName, productName, providerName, err := parseAttachString(ppRsp.Response.PurchaseUnits[0].Description)
|
||||
currency := detailRsp.Response.PurchaseUnits[0].Amount.CurrencyCode
|
||||
productDisplayName, productName, providerName, err := parseAttachString(detailRsp.Response.PurchaseUnits[0].Description)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
// TODO: status better handler, e.g.`hanging`
|
||||
var paymentStatus PaymentState
|
||||
switch detailRsp.Response.Status { // CREATED、SAVED、APPROVED、VOIDED、COMPLETED、PAYER_ACTION_REQUIRED
|
||||
case "COMPLETED":
|
||||
paymentStatus = PaymentStatePaid
|
||||
default:
|
||||
paymentStatus = PaymentStateError
|
||||
}
|
||||
notifyResult := &NotifyResult{
|
||||
PaymentStatus: paymentStatus,
|
||||
PaymentName: paymentName,
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
Price: price,
|
||||
Currency: currency,
|
||||
|
||||
OutOrderId: orderId,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
||||
func (pp *PaypalPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
|
@ -14,11 +14,34 @@
|
||||
|
||||
package pp
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PaymentState string
|
||||
|
||||
const (
|
||||
PaymentStatePaid PaymentState = "Paid"
|
||||
PaymentStateCreated PaymentState = "Created"
|
||||
PaymentStateError PaymentState = "Error"
|
||||
)
|
||||
|
||||
type NotifyResult struct {
|
||||
PaymentName string
|
||||
PaymentStatus PaymentState
|
||||
ProviderName string
|
||||
|
||||
ProductName string
|
||||
ProductDisplayName string
|
||||
Price float64
|
||||
Currency string
|
||||
|
||||
OutOrderId string
|
||||
}
|
||||
|
||||
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) (string, string, float64, string, string, error)
|
||||
Notify(request *http.Request, body []byte, authorityPublicKey string, 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
|
||||
}
|
||||
|
@ -83,22 +83,22 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
|
||||
return wxRsp.Response.CodeUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
notifyReq, err := wechat.V3ParseNotify(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := pp.Client.WxPublicKey()
|
||||
err = notifyReq.VerifySignByPK(cert)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiKey := string(pp.Client.ApiV3Key)
|
||||
result, err := notifyReq.DecryptCipherText(apiKey)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paymentName := result.OutTradeNo
|
||||
@ -106,10 +106,19 @@ func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
|
||||
productDisplayName, productName, providerName, err := parseAttachString(result.Attach)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
notifyResult := &NotifyResult{
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OutOrderId: orderId,
|
||||
Price: price,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
PaymentName: paymentName,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
|
Reference in New Issue
Block a user