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:
haiwu
2023-07-30 11:54:42 +08:00
committed by GitHub
parent 026fb207b3
commit eefa1e6df4
29 changed files with 353 additions and 172 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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, &notifyRespInfo)
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) {

View File

@ -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) {

View File

@ -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
}

View File

@ -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) {