|
- package controller
-
- import (
- "context"
- "encoding/base64"
- "fmt"
- "log"
- "net/http"
- "strconv"
- "sync"
- "time"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/model"
- "github.com/QuantumNous/new-api/setting"
-
- "github.com/gin-gonic/gin"
- "github.com/go-pay/gopay"
- "github.com/go-pay/gopay/alipay"
- "github.com/thanhpk/randstr"
- )
-
- const (
- PaymentMethodAlipay = "alipay"
- )
-
- var alipayClientMu sync.Mutex
- var alipayClient *alipay.Client
-
- // ResetAlipayClient 重置支付宝客户端(配置变更时调用)
- func ResetAlipayClient() {
- alipayClientMu.Lock()
- alipayClient = nil
- alipayClientMu.Unlock()
- }
-
- func init() {
- setting.OnAlipayConfigChanged = ResetAlipayClient
- }
-
- // getAlipayClient 获取或创建支付宝客户端
- func getAlipayClient() (*alipay.Client, error) {
- alipayClientMu.Lock()
- defer alipayClientMu.Unlock()
-
- if alipayClient != nil {
- return alipayClient, nil
- }
-
- if !setting.IsAlipayConfigured() {
- return nil, fmt.Errorf("支付宝未配置")
- }
-
- client, err := alipay.NewClient(setting.AlipayAppID, setting.AlipayPrivateKey, true)
- if err != nil {
- return nil, fmt.Errorf("创建支付宝客户端失败: %w", err)
- }
-
- client.SetCharset("utf-8").
- SetSignType(alipay.RSA2).
- SetNotifyUrl(setting.AlipayNotifyURL)
-
- // 设置支付宝公钥(用于回调验签)
- pubKeyBytes, err := base64.StdEncoding.DecodeString(setting.AlipayPublicKey)
- if err != nil {
- pubKeyBytes = []byte(setting.AlipayPublicKey)
- }
- client.AutoVerifySign([]byte(wrapAsPEM(pubKeyBytes, "PUBLIC KEY")))
-
- alipayClient = client
- return alipayClient, nil
- }
-
- // AlipayPayRequest 支付宝支付请求参数
- type AlipayPayRequest struct {
- Amount int64 `json:"amount"`
- }
-
- // createAlipayPrecreateOrder 调用支付宝当面付预下单 API,返回二维码内容
- func createAlipayPrecreateOrder(client *alipay.Client, subject, tradeNo string, totalAmount string) (string, error) {
- bm := make(gopay.BodyMap)
- bm.Set("subject", subject).
- Set("out_trade_no", tradeNo).
- Set("total_amount", totalAmount).
- Set("product_code", "FACE_TO_FACE_PAYMENT")
-
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- rsp, err := client.TradePrecreate(ctx, bm)
- if err != nil {
- return "", fmt.Errorf("支付宝当面付下单失败: %w", err)
- }
-
- if rsp.Response.Code != "10000" {
- return "", fmt.Errorf("支付宝错误: %s - %s", rsp.Response.Code, rsp.Response.Msg)
- }
-
- return rsp.Response.QrCode, nil
- }
-
- // RequestAlipayPayAmount 计算支付宝应付金额
- func RequestAlipayPayAmount(c *gin.Context) {
- var req AlipayPayRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
- return
- }
-
- minTopup := calcMinTopup(setting.AlipayMinTopUp)
- if req.Amount < minTopup {
- c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", minTopup)})
- return
- }
-
- id := c.GetInt("id")
- group, err := model.GetUserGroup(id, true)
- if err != nil {
- c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"})
- return
- }
-
- payMoney := calcPayMoney(float64(req.Amount), group, setting.AlipayUnitPrice)
- if payMoney <= 0.01 {
- c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"})
- return
- }
-
- c.JSON(200, gin.H{"message": "success", "data": strconv.FormatFloat(payMoney, 'f', 2, 64)})
- }
-
- // RequestAlipayPay 创建支付宝支付订单,返回二维码 URL
- func RequestAlipayPay(c *gin.Context) {
- var req AlipayPayRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
- return
- }
-
- minTopup := calcMinTopup(setting.AlipayMinTopUp)
- if req.Amount < minTopup {
- c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", minTopup)})
- return
- }
- if req.Amount > 10000 {
- c.JSON(200, gin.H{"message": "error", "data": "充值数量不能大于 10000"})
- return
- }
-
- if !setting.IsAlipayConfigured() {
- c.JSON(200, gin.H{"message": "error", "data": "支付宝未配置"})
- return
- }
-
- client, err := getAlipayClient()
- if err != nil {
- log.Println("获取支付宝客户端失败:", err)
- c.JSON(200, gin.H{"message": "error", "data": "支付宝配置错误"})
- return
- }
-
- id := c.GetInt("id")
-
- group, err := model.GetUserGroup(id, true)
- if err != nil {
- c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"})
- return
- }
-
- topupGroupRatio := common.GetTopupGroupRatio(group)
- if topupGroupRatio == 0 {
- topupGroupRatio = 1
- }
- chargedMoney := float64(req.Amount) * topupGroupRatio
-
- tradeNo := fmt.Sprintf("ali%d%s", time.Now().UnixMilli(), randstr.String(8))
-
- payMoney := calcPayMoney(float64(req.Amount), group, setting.AlipayUnitPrice)
- totalAmount := strconv.FormatFloat(payMoney, 'f', 2, 64)
-
- qrCode, err := createAlipayPrecreateOrder(client, fmt.Sprintf("充值%d", req.Amount), tradeNo, totalAmount)
- if err != nil {
- log.Println(err)
- c.JSON(200, gin.H{"message": "error", "data": "拉起支付失败"})
- return
- }
-
- topUp := &model.TopUp{
- UserId: id,
- Amount: req.Amount,
- Money: chargedMoney,
- TradeNo: tradeNo,
- PaymentMethod: PaymentMethodAlipay,
- CreateTime: time.Now().Unix(),
- Status: common.TopUpStatusPending,
- }
- if err := topUp.Insert(); err != nil {
- c.JSON(200, gin.H{"message": "error", "data": "创建订单失败"})
- return
- }
-
- c.JSON(200, gin.H{
- "message": "success",
- "data": gin.H{
- "trade_no": tradeNo,
- "qr_code_url": qrCode,
- },
- })
- }
-
- // AlipayPayStatus 轮询支付宝支付订单状态
- func AlipayPayStatus(c *gin.Context) {
- tradeNo := c.Query("trade_no")
- if tradeNo == "" {
- c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
- return
- }
-
- topUp := model.GetTopUpByTradeNo(tradeNo)
- if topUp == nil {
- c.JSON(200, gin.H{"message": "error", "data": "订单不存在"})
- return
- }
-
- userId := c.GetInt("id")
- if topUp.UserId != userId {
- c.JSON(200, gin.H{"message": "error", "data": "订单不存在"})
- return
- }
-
- c.JSON(200, gin.H{
- "message": "success",
- "data": gin.H{
- "status": topUp.Status,
- "amount": topUp.Amount,
- },
- })
- }
-
- // AlipayPayWebhook 处理支付宝异步回调通知
- func AlipayPayWebhook(c *gin.Context) {
- notifyReq, err := alipay.ParseNotifyToBodyMap(c.Request)
- if err != nil {
- log.Printf("解析支付宝回调失败: %v", err)
- c.String(http.StatusBadRequest, "fail")
- return
- }
-
- ok, err := alipay.VerifySign(setting.AlipayPublicKey, notifyReq)
- if err != nil {
- log.Printf("支付宝回调验签失败: %v", err)
- c.String(http.StatusBadRequest, "fail")
- return
- }
- if !ok {
- log.Printf("支付宝回调验签不通过")
- c.String(http.StatusBadRequest, "fail")
- return
- }
-
- tradeStatus := notifyReq.Get("trade_status")
- if tradeStatus != "TRADE_SUCCESS" {
- c.String(http.StatusOK, "success")
- return
- }
-
- tradeNo := notifyReq.Get("out_trade_no")
-
- LockOrder(tradeNo)
- defer UnlockOrder(tradeNo)
-
- if err := model.RechargeAlipay(tradeNo); err != nil {
- log.Printf("支付宝充值失败: %s, err: %s", tradeNo, err.Error())
- c.String(http.StatusInternalServerError, "fail")
- return
- }
-
- log.Printf("支付宝充值成功: %s", tradeNo)
- c.String(http.StatusOK, "success")
- }
|