parent
e2bfec77a4
commit
40a6a5d8d8
8
go.mod
8
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1
|
||||
github.com/aws/smithy-go v1.20.1
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/gin-contrib/cors v1.7.0
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-contrib/sessions v0.0.4
|
||||
@ -21,7 +22,9 @@ require (
|
||||
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/smartwalle/alipay/v3 v3.2.21
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/wneessen/go-mail v0.4.1
|
||||
@ -35,17 +38,18 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
||||
github.com/smartwalle/ngx v1.0.9 // indirect
|
||||
github.com/smartwalle/nsign v1.0.9 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -189,6 +189,14 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/smartwalle/alipay/v3 v3.2.21 h1:zQmzYOGU4WCc2f21VzqBqC60iEJ6meVxe1Sl33nJ+vE=
|
||||
github.com/smartwalle/alipay/v3 v3.2.21/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
||||
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
||||
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
|
||||
github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
|
||||
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
|
||||
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
|
||||
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
|
173
payment/gateway/alipay/payment.go
Normal file
173
payment/gateway/alipay/payment.go
Normal file
@ -0,0 +1,173 @@
|
||||
package alipay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"net/http"
|
||||
"net/url"
|
||||
sysconfig "one-api/common/config"
|
||||
"one-api/payment/types"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Alipay struct{}
|
||||
|
||||
type AlipayConfig struct {
|
||||
AppID string `json:"app_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
PublicKey string `json:"public_key"`
|
||||
PayType PayType `json:"pay_type"`
|
||||
}
|
||||
|
||||
var client *alipay.Client
|
||||
|
||||
const isProduction bool = true
|
||||
|
||||
func (a *Alipay) Name() string {
|
||||
return "支付宝当面付"
|
||||
}
|
||||
|
||||
func (a *Alipay) InitClient(config *AlipayConfig) error {
|
||||
var err error
|
||||
client, err = alipay.New(config.AppID, config.PrivateKey, isProduction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.LoadAliPayPublicKey(config.PublicKey)
|
||||
}
|
||||
|
||||
func (a *Alipay) Pay(config *types.PayConfig, gatewayConfig string) (*types.PayRequest, error) {
|
||||
alipayConfig, err := getAlipayConfig(gatewayConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
err := a.InitClient(alipayConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if alipayConfig.PayType != PagePay {
|
||||
var p = alipay.TradePreCreate{}
|
||||
p.OutTradeNo = config.TradeNo
|
||||
p.TotalAmount = strconv.FormatFloat(config.Money, 'f', 2, 64)
|
||||
p.Subject = sysconfig.SystemName + "-Token充值:" + p.TotalAmount
|
||||
p.NotifyURL = config.NotifyURL
|
||||
p.ReturnURL = config.ReturnURL
|
||||
ctx := context.Background()
|
||||
alipayRes, err := client.TradePreCreate(ctx, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alipay trade precreate failed: %s", alipayRes.Msg)
|
||||
}
|
||||
if !alipayRes.IsSuccess() {
|
||||
return nil, fmt.Errorf("alipay trade precreate failed: %s", alipayRes.Msg)
|
||||
}
|
||||
if alipayRes.Code != "10000" {
|
||||
return nil, fmt.Errorf("alipay trade precreate failed: %s", alipayRes.Msg)
|
||||
}
|
||||
payRequest := &types.PayRequest{
|
||||
Type: 2,
|
||||
Data: types.PayRequestData{
|
||||
URL: alipayRes.QRCode,
|
||||
Method: http.MethodGet,
|
||||
},
|
||||
}
|
||||
return payRequest, nil
|
||||
} else {
|
||||
var p = alipay.TradePagePay{}
|
||||
p.OutTradeNo = config.TradeNo
|
||||
p.TotalAmount = strconv.FormatFloat(config.Money, 'f', 2, 64)
|
||||
p.Subject = sysconfig.SystemName + "-Token充值:" + p.TotalAmount
|
||||
p.NotifyURL = config.NotifyURL
|
||||
p.ReturnURL = config.ReturnURL
|
||||
alipayRes, err := client.TradePagePay(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alipay trade precreate failed: %s", err.Error())
|
||||
}
|
||||
payUrl, parms, err := extractURLAndParams(alipayRes.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alipay trade precreate failed: %s", err.Error())
|
||||
}
|
||||
payRequest := &types.PayRequest{
|
||||
Type: 1,
|
||||
Data: types.PayRequestData{
|
||||
URL: payUrl,
|
||||
Params: parms,
|
||||
Method: http.MethodGet,
|
||||
},
|
||||
}
|
||||
return payRequest, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *Alipay) HandleCallback(c *gin.Context, gatewayConfig string) (*types.PayNotify, error) {
|
||||
// 获取通知参数
|
||||
params := c.Request.URL.Query()
|
||||
if err := c.Request.ParseForm(); err != nil {
|
||||
c.Writer.Write([]byte("failure"))
|
||||
return nil, fmt.Errorf("Alipay params failed: %v", err)
|
||||
}
|
||||
for k, v := range c.Request.PostForm {
|
||||
params[k] = v
|
||||
}
|
||||
// 验证通知签名
|
||||
if err := client.VerifySign(params); err != nil {
|
||||
c.Writer.Write([]byte("failure"))
|
||||
return nil, fmt.Errorf("Alipay Signature verification failed: %v", err)
|
||||
}
|
||||
//解析通知内容
|
||||
var noti, err = client.DecodeNotification(params)
|
||||
if err != nil {
|
||||
c.Writer.Write([]byte("failure"))
|
||||
return nil, fmt.Errorf("Alipay Error decoding notification: %v", err)
|
||||
}
|
||||
|
||||
if noti.TradeStatus == alipay.TradeStatusSuccess {
|
||||
payNotify := &types.PayNotify{
|
||||
TradeNo: noti.OutTradeNo,
|
||||
GatewayNo: noti.TradeNo,
|
||||
}
|
||||
alipay.ACKNotification(c.Writer)
|
||||
return payNotify, nil
|
||||
}
|
||||
c.Writer.Write([]byte("failure"))
|
||||
return nil, fmt.Errorf("trade status not success")
|
||||
}
|
||||
|
||||
func getAlipayConfig(gatewayConfig string) (*AlipayConfig, error) {
|
||||
var alipayConfig AlipayConfig
|
||||
if err := json.Unmarshal([]byte(gatewayConfig), &alipayConfig); err != nil {
|
||||
return nil, errors.New("config error")
|
||||
}
|
||||
|
||||
return &alipayConfig, nil
|
||||
}
|
||||
|
||||
// extractURLAndParams 从给定的原始 URL 中提取网址和参数,并将参数转换为 map[string]string
|
||||
func extractURLAndParams(rawURL string) (string, map[string]string, error) {
|
||||
// 解析 URL
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// 提取网址
|
||||
baseURL := fmt.Sprintf("%s://%s%s", parsedURL.Scheme, parsedURL.Host, parsedURL.Path)
|
||||
|
||||
// 提取参数并转换成 map[string]string
|
||||
params := parsedURL.Query()
|
||||
paramMap := make(map[string]string)
|
||||
for key, values := range params {
|
||||
// 由于 URL 参数可能有多个值,这里只取第一个值
|
||||
paramMap[key] = values[0]
|
||||
}
|
||||
|
||||
return baseURL, paramMap, nil
|
||||
}
|
25
payment/gateway/alipay/type.go
Normal file
25
payment/gateway/alipay/type.go
Normal file
@ -0,0 +1,25 @@
|
||||
package alipay
|
||||
|
||||
type PayType string
|
||||
|
||||
var (
|
||||
FacePay PayType = "facepay" // 当面付
|
||||
PagePay PayType = "pagepay" // 跳转支付
|
||||
|
||||
)
|
||||
|
||||
type PayArgs struct {
|
||||
AppID string `json:"app_id"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
NotifyUrl string `json:"notify_url"`
|
||||
ReturnUrl string `json:"return_url"`
|
||||
Subject string `json:"subject"`
|
||||
TotalAmount string `json:"total_amount"`
|
||||
}
|
||||
|
||||
type PaymentResult struct {
|
||||
OutTradeNo string `mapstructure:"out_trade_no"`
|
||||
TradeNo string `mapstructure:"trade_no"`
|
||||
TotalAmount string `mapstructure:"total_amount"`
|
||||
TradeStatus string `mapstructure:"trade_status"`
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"one-api/payment/gateway/alipay"
|
||||
"one-api/payment/gateway/epay"
|
||||
"one-api/payment/types"
|
||||
|
||||
@ -17,4 +18,5 @@ var Gateways = make(map[string]PaymentProcessor)
|
||||
|
||||
func init() {
|
||||
Gateways["epay"] = &epay.Epay{}
|
||||
Gateways["alipay"] = &alipay.Alipay{}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
const PaymentType = {
|
||||
epay: '易支付'
|
||||
epay: '易支付',
|
||||
alipay: '支付宝'
|
||||
};
|
||||
|
||||
const CurrencyType = {
|
||||
@ -67,6 +68,42 @@ const PaymentConfig = {
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
alipay: {
|
||||
app_id: {
|
||||
name: '应用ID',
|
||||
description: '支付宝应用ID',
|
||||
type: 'text',
|
||||
value: ''
|
||||
},
|
||||
private_key: {
|
||||
name: '应用私钥',
|
||||
description: '应用私钥,开发者自己生成,详细参考官方文档 https://opendocs.alipay.com/common/02kipl?pathHash=84adb0fd',
|
||||
type: 'text',
|
||||
value: ''
|
||||
},
|
||||
public_key: {
|
||||
name: '支付宝公钥',
|
||||
description: '支付宝公钥,详细参考官方文档 https://opendocs.alipay.com/common/02kdnc?pathHash=fb0c752a',
|
||||
type: 'text',
|
||||
value: ''
|
||||
},
|
||||
pay_type: {
|
||||
name: '支付类型',
|
||||
description: '支付类型,需要您再支付宝开发者中心开通相关权限才可以使用对应类型支付方式',
|
||||
type: 'select',
|
||||
value: '',
|
||||
options: [
|
||||
{
|
||||
name: '当面付',
|
||||
value: 'facepay'
|
||||
},
|
||||
{
|
||||
name: '跳转支付',
|
||||
value: 'pagepay'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user