mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-02 03:00:18 +08:00
feat: support Stripe payment provider (#2204)
* feat: add stripe payment provider * feat: support stripe payment * feat: delete todo comment * feat: remove description struct * feat: change outOrderId->orderId
This commit is contained in:
@ -90,7 +90,7 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OutOrderId: orderId,
|
||||
OrderId: orderId,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
Price: price,
|
||||
PaymentName: paymentName,
|
||||
|
2
pp/gc.go
2
pp/gc.go
@ -255,7 +255,7 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OutOrderId: orderId,
|
||||
OrderId: orderId,
|
||||
Price: price,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
PaymentName: paymentName,
|
||||
|
12
pp/paypal.go
12
pp/paypal.go
@ -52,13 +52,12 @@ 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) {
|
||||
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md
|
||||
priceStr := strconv.FormatFloat(price, 'f', 2, 64)
|
||||
units := make([]*paypal.PurchaseUnit, 0, 1)
|
||||
unit := &paypal.PurchaseUnit{
|
||||
ReferenceId: util.GetRandomString(16),
|
||||
Amount: &paypal.Amount{
|
||||
CurrencyCode: currency, // e.g."USD"
|
||||
Value: priceStr, // e.g."100.00"
|
||||
CurrencyCode: currency, // e.g."USD"
|
||||
Value: priceFloat64ToString(price), // e.g."100.00"
|
||||
},
|
||||
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
|
||||
}
|
||||
@ -147,16 +146,15 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
paymentStatus = PaymentStateError
|
||||
}
|
||||
notifyResult = &NotifyResult{
|
||||
PaymentStatus: paymentStatus,
|
||||
PaymentName: paymentName,
|
||||
|
||||
PaymentStatus: paymentStatus,
|
||||
PaymentName: paymentName,
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
Price: price,
|
||||
Currency: currency,
|
||||
|
||||
OutOrderId: orderId,
|
||||
OrderId: orderId,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ const (
|
||||
PaymentStatePaid PaymentState = "Paid"
|
||||
PaymentStateCreated PaymentState = "Created"
|
||||
PaymentStateCanceled PaymentState = "Canceled"
|
||||
PaymentStateTimeout PaymentState = "Timeout"
|
||||
PaymentStateError PaymentState = "Error"
|
||||
)
|
||||
|
||||
@ -32,13 +33,13 @@ type NotifyResult struct {
|
||||
PaymentStatus PaymentState
|
||||
NotifyMessage string
|
||||
|
||||
ProviderName string
|
||||
ProductName string
|
||||
ProductDisplayName string
|
||||
ProviderName string
|
||||
Price float64
|
||||
Currency string
|
||||
|
||||
OutOrderId string
|
||||
OrderId string
|
||||
}
|
||||
|
||||
type PaymentProvider interface {
|
||||
@ -75,6 +76,12 @@ func GetPaymentProvider(typ string, clientId string, clientSecret string, host s
|
||||
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
|
||||
|
166
pp/stripe.go
Normal file
166
pp/stripe.go
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/stripe/stripe-go/v74"
|
||||
stripeCheckout "github.com/stripe/stripe-go/v74/checkout/session"
|
||||
stripeIntent "github.com/stripe/stripe-go/v74/paymentintent"
|
||||
stripePrice "github.com/stripe/stripe-go/v74/price"
|
||||
stripeProduct "github.com/stripe/stripe-go/v74/product"
|
||||
)
|
||||
|
||||
type StripePaymentProvider struct {
|
||||
PublishableKey string
|
||||
SecretKey string
|
||||
isProd bool
|
||||
}
|
||||
|
||||
func NewStripePaymentProvider(PublishableKey, SecretKey string) (*StripePaymentProvider, error) {
|
||||
isProd := false
|
||||
if conf.GetConfigString("runmode") == "prod" {
|
||||
isProd = true
|
||||
}
|
||||
pp := &StripePaymentProvider{
|
||||
PublishableKey: PublishableKey,
|
||||
SecretKey: SecretKey,
|
||||
isProd: isProd,
|
||||
}
|
||||
stripe.Key = pp.SecretKey
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *StripePaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (payUrl string, orderId string, err error) {
|
||||
// Create a temp product
|
||||
description := joinAttachString([]string{productName, productDisplayName, providerName})
|
||||
productParams := &stripe.ProductParams{
|
||||
Name: stripe.String(productDisplayName),
|
||||
Description: stripe.String(description),
|
||||
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
|
||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
|
||||
Currency: stripe.String(currency),
|
||||
},
|
||||
}
|
||||
sProduct, err := stripeProduct.New(productParams)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
// Create a price for an existing product
|
||||
priceParams := &stripe.PriceParams{
|
||||
Currency: stripe.String(currency),
|
||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
|
||||
Product: stripe.String(sProduct.ID),
|
||||
}
|
||||
sPrice, err := stripePrice.New(priceParams)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
// Create a Checkout Session
|
||||
checkoutParams := &stripe.CheckoutSessionParams{
|
||||
LineItems: []*stripe.CheckoutSessionLineItemParams{
|
||||
{
|
||||
Price: stripe.String(sPrice.ID),
|
||||
Quantity: stripe.Int64(1),
|
||||
},
|
||||
},
|
||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||
SuccessURL: stripe.String(returnUrl),
|
||||
CancelURL: stripe.String(returnUrl),
|
||||
ClientReferenceID: stripe.String(paymentName),
|
||||
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
|
||||
}
|
||||
checkoutParams.AddMetadata("product_description", description)
|
||||
sCheckout, err := stripeCheckout.New(checkoutParams)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return sCheckout.URL, sCheckout.ID, nil
|
||||
}
|
||||
|
||||
func (pp *StripePaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
notifyResult := &NotifyResult{}
|
||||
sCheckout, err := stripeCheckout.Get(orderId, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch sCheckout.Status {
|
||||
case "open":
|
||||
// The checkout session is still in progress. Payment processing has not started
|
||||
notifyResult.PaymentStatus = PaymentStateCreated
|
||||
return notifyResult, nil
|
||||
case "complete":
|
||||
// The checkout session is complete. Payment processing may still be in progress
|
||||
case "expired":
|
||||
// The checkout session has expired. No further processing will occur
|
||||
notifyResult.PaymentStatus = PaymentStateTimeout
|
||||
return notifyResult, nil
|
||||
default:
|
||||
notifyResult.PaymentStatus = PaymentStateError
|
||||
notifyResult.NotifyMessage = fmt.Sprintf("unexpected stripe checkout status: %v", sCheckout.Status)
|
||||
return notifyResult, nil
|
||||
}
|
||||
switch sCheckout.PaymentStatus {
|
||||
case "paid":
|
||||
// Skip
|
||||
case "unpaid":
|
||||
notifyResult.PaymentStatus = PaymentStateCreated
|
||||
return notifyResult, nil
|
||||
default:
|
||||
notifyResult.PaymentStatus = PaymentStateError
|
||||
notifyResult.NotifyMessage = fmt.Sprintf("unexpected stripe checkout payment status: %v", sCheckout.PaymentStatus)
|
||||
return notifyResult, nil
|
||||
}
|
||||
// Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent`
|
||||
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
|
||||
var (
|
||||
productName string
|
||||
productDisplayName string
|
||||
providerName string
|
||||
)
|
||||
if description, ok := sCheckout.Metadata["product_description"]; ok {
|
||||
productName, productDisplayName, providerName, _ = parseAttachString(description)
|
||||
}
|
||||
notifyResult = &NotifyResult{
|
||||
PaymentName: sCheckout.ClientReferenceID,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
|
||||
Price: priceInt64ToFloat64(sIntent.Amount),
|
||||
Currency: string(sIntent.Currency),
|
||||
|
||||
OrderId: orderId,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
||||
func (pp *StripePaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (pp *StripePaymentProvider) GetResponseError(err error) string {
|
||||
if err == nil {
|
||||
return "success"
|
||||
} else {
|
||||
return "fail"
|
||||
}
|
||||
}
|
14
pp/util.go
14
pp/util.go
@ -16,6 +16,8 @@ package pp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -35,3 +37,15 @@ func parseAttachString(s string) (string, string, string, error) {
|
||||
}
|
||||
return tokens[0], tokens[1], tokens[2], nil
|
||||
}
|
||||
|
||||
func priceInt64ToFloat64(price int64) float64 {
|
||||
return float64(price) / 100
|
||||
}
|
||||
|
||||
func priceFloat64ToInt64(price float64) int64 {
|
||||
return int64(math.Round(price * 100))
|
||||
}
|
||||
|
||||
func priceFloat64ToString(price float64) string {
|
||||
return strconv.FormatFloat(price, 'f', 2, 64)
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OutOrderId: orderId,
|
||||
OrderId: orderId,
|
||||
Price: price,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
PaymentName: paymentName,
|
||||
|
Reference in New Issue
Block a user