one-api/payment/gateway/alipay/payment.go
ZeroDeng 40a6a5d8d8
feat: add Alipay payment (#251)
增加支付宝当面付(扫码支付)和网页跳转支付功能
2024-06-06 01:11:06 +08:00

174 lines
4.6 KiB
Go

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
}