diff --git a/controllers/payment.go b/controllers/payment.go index 8cafdb8d..47e15a24 100644 --- a/controllers/payment.go +++ b/controllers/payment.go @@ -178,10 +178,11 @@ func (c *ApiController) NotifyPayment() { providerName := c.Ctx.Input.Param(":provider") productName := c.Ctx.Input.Param(":product") paymentName := c.Ctx.Input.Param(":payment") + orderId := c.Ctx.Input.Param("order") body := c.Ctx.Input.RequestBody - err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName) + err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName, orderId) _, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse)) if err2 != nil { diff --git a/controllers/product.go b/controllers/product.go index b33cbe59..e0cb6a5b 100644 --- a/controllers/product.go +++ b/controllers/product.go @@ -180,11 +180,11 @@ func (c *ApiController) BuyProduct() { return } - payUrl, err := object.BuyProduct(id, providerName, user, host) + payUrl, orderId, err := object.BuyProduct(id, providerName, user, host) if err != nil { c.ResponseError(err.Error()) return } - c.ResponseOk(payUrl) + c.ResponseOk(payUrl, orderId) } diff --git a/object/payment.go b/object/payment.go index 64168141..c16c36dd 100644 --- a/object/payment.go +++ b/object/payment.go @@ -149,7 +149,7 @@ func DeletePayment(payment *Payment) (bool, error) { return affected != 0, nil } -func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error, string) { +func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (*Payment, error, string) { provider, err := getProvider(owner, providerName) if err != nil { panic(err) @@ -180,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam return payment, err, pProvider.GetResponseError(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, orderId) if err != nil { return payment, err, pProvider.GetResponseError(err) } @@ -199,8 +199,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam return payment, err, pProvider.GetResponseError(err) } -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) +func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (error, string) { + payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName, orderId) if payment != nil { if err != nil { payment.State = "Error" diff --git a/object/product.go b/object/product.go index 413509f5..dd7fce78 100644 --- a/object/product.go +++ b/object/product.go @@ -156,24 +156,24 @@ func (product *Product) getProvider(providerId string) (*Provider, error) { return provider, nil } -func BuyProduct(id string, providerName string, user *User, host string) (string, error) { +func BuyProduct(id string, providerName string, user *User, host string) (string, string, error) { product, err := GetProduct(id) if err != nil { - return "", err + return "", "", err } if product == nil { - return "", fmt.Errorf("the product: %s does not exist", id) + return "", "", fmt.Errorf("the product: %s does not exist", id) } provider, err := product.getProvider(providerName) if err != nil { - return "", err + return "", "", err } pProvider, _, err := provider.getPaymentProvider() if err != nil { - return "", err + return "", "", err } owner := product.Owner @@ -186,9 +186,9 @@ func BuyProduct(id string, providerName string, user *User, host string) (string returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName) notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName) - payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl) + payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl) if err != nil { - return "", err + return "", "", err } payment := Payment{ @@ -217,14 +217,14 @@ func BuyProduct(id string, providerName string, user *User, host string) (string affected, err := AddPayment(&payment) if err != nil { - return "", err + return "", "", err } if !affected { - return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) + return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) } - return payUrl, err + return payUrl, orderId, err } func ExtendProductWithProviders(product *Product) error { diff --git a/object/product_test.go b/object/product_test.go index 43ecb9ed..5271a30e 100644 --- a/object/product_test.go +++ b/object/product_test.go @@ -38,7 +38,7 @@ func TestProduct(t *testing.T) { paymentName := util.GenerateTimeId() returnUrl := "" notifyUrl := "" - payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl) + payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl) if err != nil { panic(err) } diff --git a/pp/alipay.go b/pp/alipay.go index 21b453e2..4f790afb 100644 --- a/pp/alipay.go +++ b/pp/alipay.go @@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey return pp, nil } -func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl 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 bm := gopay.BodyMap{} @@ -62,12 +62,12 @@ func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, pa payUrl, err := pp.Client.TradePagePay(context.Background(), bm) if err != nil { - return "", err + return "", "", err } - return payUrl, nil + return payUrl, "", nil } -func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { +func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) { bm, err := alipay.ParseNotifyToBodyMap(request) if err != nil { return "", "", 0, "", "", err diff --git a/pp/dummy.go b/pp/dummy.go index a0c3e6e4..8f53e52c 100644 --- a/pp/dummy.go +++ b/pp/dummy.go @@ -26,12 +26,12 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) { return pp, nil } -func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { +func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { payUrl := fmt.Sprintf("/payments/%s/result", paymentName) - return payUrl, nil + return payUrl, "", nil } -func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { +func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) { return "", "", 0, "", "", nil } diff --git a/pp/gc.go b/pp/gc.go index 06929387..625f7a73 100644 --- a/pp/gc.go +++ b/pp/gc.go @@ -153,7 +153,7 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) { return respBytes, nil } -func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { +func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { payReqInfo := GcPayReqInfo{ OrderDate: util.GenerateSimpleTimeId(), OrderNo: paymentName, @@ -168,7 +168,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN b, err := json.Marshal(payReqInfo) if err != nil { - return "", err + return "", "", err } body := GcRequestBody{ @@ -184,39 +184,39 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN bodyBytes, err := json.Marshal(body) if err != nil { - return "", err + return "", "", err } respBytes, err := pp.doPost(bodyBytes) if err != nil { - return "", err + return "", "", err } var respBody GcResponseBody err = json.Unmarshal(respBytes, &respBody) if err != nil { - return "", err + return "", "", err } if respBody.ReturnCode != "SUCCESS" { - return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg) + return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg) } payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data) if err != nil { - return "", err + return "", "", err } var payRespInfo GcPayRespInfo err = json.Unmarshal(payRespInfoBytes, &payRespInfo) if err != nil { - return "", err + return "", "", err } - return payRespInfo.PayUrl, nil + return payRespInfo.PayUrl, "", nil } -func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { +func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) { reqBody := GcRequestBody{} m, err := url.ParseQuery(string(body)) if err != nil { diff --git a/pp/paypal.go b/pp/paypal.go index d715600b..0772084f 100644 --- a/pp/paypal.go +++ b/pp/paypal.go @@ -16,10 +16,13 @@ package pp import ( "context" - "fmt" + "errors" "net/http" + "strconv" - "github.com/plutov/paypal/v4" + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/paypal" + "github.com/go-pay/gopay/pkg/util" ) type PaypalPaymentProvider struct { @@ -29,7 +32,7 @@ type PaypalPaymentProvider struct { func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentProvider, error) { pp := &PaypalPaymentProvider{} - client, err := paypal.NewClient(clientID, secret, paypal.APIBaseSandBox) + client, err := paypal.NewClient(clientID, secret, false) if err != nil { return nil, err } @@ -38,51 +41,62 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro return pp, nil } -func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { - // pp.Client.SetLog(os.Stdout) // Set log to terminal stdout +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 - receiverEmail := "sb-tmsqa26118644@business.example.com" - - amount := paypal.AmountPayout{ - Value: fmt.Sprintf("%.2f", price), - Currency: "USD", - } - - description := fmt.Sprintf("%s-%s", providerName, productName) - - payout := paypal.Payout{ - SenderBatchHeader: &paypal.SenderBatchHeader{ - EmailSubject: description, - }, - Items: []paypal.PayoutItem{ - { - RecipientType: "EMAIL", - Receiver: receiverEmail, - Amount: &amount, - Note: description, - SenderItemID: description, - }, + priceStr := strconv.FormatFloat(price, 'f', 2, 64) + var pus []*paypal.PurchaseUnit + item := &paypal.PurchaseUnit{ + ReferenceId: util.GetRandomString(16), + Amount: &paypal.Amount{ + CurrencyCode: currency, + Value: priceStr, }, + Description: joinAttachString([]string{productDisplayName, productName, providerName}), } + pus = append(pus, item) - _, err := pp.Client.GetAccessToken(context.Background()) + bm := make(gopay.BodyMap) + bm.Set("intent", "CAPTURE") + bm.Set("purchase_units", pus) + bm.SetBodyMap("payment_source", func(b1 gopay.BodyMap) { + b1.SetBodyMap("paypal", func(b2 gopay.BodyMap) { + b2.Set("brand_name", "Casdoor") + b2.Set("return_url", returnUrl) + }) + }) + ppRsp, err := pp.Client.CreateOrder(context.Background(), bm) if err != nil { - return "", err + return "", "", err + } + if ppRsp.Code != paypal.Success { + return "", "", errors.New(ppRsp.Error) } - payoutResponse, err := pp.Client.CreatePayout(context.Background(), payout) - if err != nil { - return "", err - } - - payUrl := payoutResponse.Links[0].Href - return payUrl, nil + return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil } -func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) { - // The PayPal SDK does not directly support IPN verification. - // So, you need to implement this part according to PayPal's IPN guide. - return "", "", 0, "", "", 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) + if err != nil { + return "", "", 0, "", "", err + } + if ppRsp.Code != paypal.Success { + return "", "", 0, "", "", errors.New(ppRsp.Error) + } + + paymentName := ppRsp.Response.Id + price, err := strconv.ParseFloat(ppRsp.Response.PurchaseUnits[0].Amount.Value, 64) + if err != nil { + return "", "", 0, "", "", err + } + + productDisplayName, productName, providerName, err := parseAttachString(ppRsp.Response.PurchaseUnits[0].Description) + if err != nil { + return "", "", 0, "", "", err + } + + return productDisplayName, paymentName, price, productName, providerName, nil } func (pp *PaypalPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) { diff --git a/pp/provider.go b/pp/provider.go index 4a176a12..7b83c2f0 100644 --- a/pp/provider.go +++ b/pp/provider.go @@ -17,8 +17,8 @@ package pp import "net/http" 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) + 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) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetResponseError(err error) string } diff --git a/pp/wechatpay.go b/pp/wechatpay.go index 0b2e1889..371adf41 100644 --- a/pp/wechatpay.go +++ b/pp/wechatpay.go @@ -56,7 +56,7 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe return pp, nil } -func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { +func (pp *WechatPaymentProvider) 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 bm := gopay.BodyMap{} @@ -73,17 +73,17 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm) if err != nil { - return "", err + return "", "", err } if wxRsp.Code != wechat.Success { - return "", errors.New(wxRsp.Error) + return "", "", errors.New(wxRsp.Error) } - return wxRsp.Response.CodeUrl, nil + 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, orderId string) (string, string, float64, string, string, error) { notifyReq, err := wechat.V3ParseNotify(request) if err != nil { panic(err)