2024-11-21 12:14:34 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-04-09 17:27:40 +08:00
|
|
|
|
"crypto/rand"
|
|
|
|
|
"encoding/hex"
|
2024-11-21 12:14:34 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
2025-04-09 17:27:40 +08:00
|
|
|
|
"sync/atomic"
|
2024-11-21 12:14:34 +08:00
|
|
|
|
"time"
|
2025-04-09 15:58:06 +08:00
|
|
|
|
"tyc-server/app/user/cmd/api/internal/config"
|
|
|
|
|
"tyc-server/pkg/lzkit/lzUtils"
|
|
|
|
|
|
|
|
|
|
"github.com/smartwalle/alipay/v3"
|
2024-11-21 12:14:34 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AliPayService struct {
|
|
|
|
|
config config.AlipayConfig
|
|
|
|
|
AlipayClient *alipay.Client
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewAliPayService 是一个构造函数,用于初始化 AliPayService
|
|
|
|
|
func NewAliPayService(c config.Config) *AliPayService {
|
|
|
|
|
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(fmt.Sprintf("创建支付宝客户端失败: %v", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载支付宝公钥
|
|
|
|
|
err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(fmt.Sprintf("加载支付宝公钥失败: %v", err))
|
|
|
|
|
}
|
|
|
|
|
return &AliPayService{
|
|
|
|
|
config: c.Alipay,
|
|
|
|
|
AlipayClient: client,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) {
|
|
|
|
|
client := a.AlipayClient
|
|
|
|
|
totalAmount := lzUtils.ToAlipayAmount(amount)
|
|
|
|
|
// 构造移动支付请求
|
|
|
|
|
p := alipay.TradeAppPay{
|
|
|
|
|
Trade: alipay.Trade{
|
|
|
|
|
Subject: subject,
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
TotalAmount: totalAmount,
|
|
|
|
|
ProductCode: "QUICK_MSECURITY_PAY", // 移动端支付专用代码
|
|
|
|
|
NotifyURL: a.config.NotifyUrl, // 异步回调通知地址
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取APP支付字符串,这里会签名
|
|
|
|
|
payStr, err := client.TradeAppPay(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("创建支付宝订单失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return payStr, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-24 11:37:25 +08:00
|
|
|
|
// CreateAlipayH5Order 创建支付宝H5支付订单
|
2025-01-04 21:24:26 +08:00
|
|
|
|
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string, brand string) (string, error) {
|
|
|
|
|
var returnURL string
|
2025-02-18 15:23:26 +08:00
|
|
|
|
var notifyURL string
|
2025-01-04 21:24:26 +08:00
|
|
|
|
if brand == "tyc" {
|
2025-02-18 17:35:00 +08:00
|
|
|
|
returnURL = "https://www.tianyuancha.cn/report"
|
2025-02-18 17:45:09 +08:00
|
|
|
|
notifyURL = "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
|
2025-01-04 21:24:26 +08:00
|
|
|
|
} else {
|
|
|
|
|
returnURL = a.config.ReturnURL
|
2025-02-18 15:23:26 +08:00
|
|
|
|
notifyURL = a.config.NotifyUrl
|
2025-01-04 21:24:26 +08:00
|
|
|
|
}
|
2024-12-24 11:37:25 +08:00
|
|
|
|
client := a.AlipayClient
|
|
|
|
|
totalAmount := lzUtils.ToAlipayAmount(amount)
|
|
|
|
|
// 构造H5支付请求
|
|
|
|
|
p := alipay.TradeWapPay{
|
2024-12-30 16:55:06 +08:00
|
|
|
|
Trade: alipay.Trade{
|
|
|
|
|
Subject: subject,
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
TotalAmount: totalAmount,
|
2025-02-18 15:23:26 +08:00
|
|
|
|
ProductCode: "QUICK_WAP_PAY", // H5支付专用产品码
|
|
|
|
|
NotifyURL: notifyURL, // 异步回调通知地址
|
2025-01-04 21:24:26 +08:00
|
|
|
|
ReturnURL: returnURL,
|
2024-12-30 16:55:06 +08:00
|
|
|
|
},
|
2024-12-24 11:37:25 +08:00
|
|
|
|
}
|
|
|
|
|
// 获取H5支付请求字符串,这里会签名
|
|
|
|
|
payUrl, err := client.TradeWapPay(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("创建支付宝H5订单失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return payUrl.String(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
|
2025-01-04 21:24:26 +08:00
|
|
|
|
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string, brand string) (string, error) {
|
2024-12-24 11:37:25 +08:00
|
|
|
|
// 根据 ctx 中的 platform 判断平台
|
|
|
|
|
platform, platformOk := ctx.Value("platform").(string)
|
|
|
|
|
if !platformOk {
|
|
|
|
|
return "", fmt.Errorf("无的支付平台: %s", platform)
|
|
|
|
|
}
|
|
|
|
|
switch platform {
|
|
|
|
|
case "app":
|
|
|
|
|
// 调用App支付的创建方法
|
2024-12-30 17:56:03 +08:00
|
|
|
|
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
|
2024-12-24 11:37:25 +08:00
|
|
|
|
case "h5":
|
|
|
|
|
// 调用H5支付的创建方法,并传入 returnUrl
|
2025-01-04 21:24:26 +08:00
|
|
|
|
return a.CreateAlipayH5Order(amount, subject, outTradeNo, brand)
|
2024-12-24 11:37:25 +08:00
|
|
|
|
default:
|
|
|
|
|
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 12:14:34 +08:00
|
|
|
|
// AliRefund 发起支付宝退款
|
|
|
|
|
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64) (*alipay.TradeRefundRsp, error) {
|
|
|
|
|
refund := alipay.TradeRefund{
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
RefundAmount: lzUtils.ToAlipayAmount(refundAmount),
|
|
|
|
|
OutRequestNo: fmt.Sprintf("%s-refund", outTradeNo),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发起退款请求
|
|
|
|
|
refundResp, err := a.AlipayClient.TradeRefund(ctx, refund)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("支付宝退款请求错误:%v", err)
|
|
|
|
|
}
|
|
|
|
|
return refundResp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HandleAliPaymentNotification 支付宝支付回调
|
|
|
|
|
func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) {
|
|
|
|
|
// 解析表单
|
|
|
|
|
err := r.ParseForm()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("解析请求表单失败:%v", err)
|
|
|
|
|
}
|
|
|
|
|
// 解析并验证通知,DecodeNotification 会自动验证签名
|
|
|
|
|
notification, err := a.AlipayClient.DecodeNotification(r.Form)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("验证签名失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return notification, nil
|
|
|
|
|
}
|
|
|
|
|
func (a *AliPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*alipay.TradeQueryRsp, error) {
|
|
|
|
|
queryRequest := alipay.TradeQuery{
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发起查询请求
|
|
|
|
|
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回交易状态
|
|
|
|
|
if resp.IsSuccess() {
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("查询支付宝订单失败: %v", resp.SubMsg)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-09 17:27:40 +08:00
|
|
|
|
// 添加全局原子计数器
|
|
|
|
|
var alipayOrderCounter uint32 = 0
|
|
|
|
|
|
|
|
|
|
// GenerateOutTradeNo 生成唯一订单号的函数 - 优化版本
|
2024-11-21 12:14:34 +08:00
|
|
|
|
func (a *AliPayService) GenerateOutTradeNo() string {
|
|
|
|
|
|
2025-04-09 17:27:40 +08:00
|
|
|
|
// 获取当前时间戳(毫秒级)
|
|
|
|
|
timestamp := time.Now().UnixMilli()
|
2024-11-21 12:14:34 +08:00
|
|
|
|
timeStr := strconv.FormatInt(timestamp, 10)
|
|
|
|
|
|
2025-04-09 17:27:40 +08:00
|
|
|
|
// 原子递增计数器
|
|
|
|
|
counter := atomic.AddUint32(&alipayOrderCounter, 1)
|
2024-11-21 12:14:34 +08:00
|
|
|
|
|
2025-04-09 17:27:40 +08:00
|
|
|
|
// 生成4字节真随机数
|
|
|
|
|
randomBytes := make([]byte, 4)
|
|
|
|
|
_, err := rand.Read(randomBytes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// 如果随机数生成失败,回退到使用时间纳秒数据
|
|
|
|
|
randomBytes = []byte(strconv.FormatInt(time.Now().UnixNano()%1000000, 16))
|
2024-11-21 12:14:34 +08:00
|
|
|
|
}
|
2025-04-09 17:27:40 +08:00
|
|
|
|
randomHex := hex.EncodeToString(randomBytes)
|
|
|
|
|
|
|
|
|
|
// 组合所有部分: 前缀 + 时间戳 + 计数器 + 随机数
|
|
|
|
|
orderNo := fmt.Sprintf("%s%06x%s", timeStr[:10], counter%0xFFFFFF, randomHex[:6])
|
2024-11-21 12:14:34 +08:00
|
|
|
|
|
2025-04-09 17:27:40 +08:00
|
|
|
|
// 确保长度不超过32字符(大多数支付平台的限制)
|
|
|
|
|
if len(orderNo) > 32 {
|
|
|
|
|
orderNo = orderNo[:32]
|
2024-11-21 12:14:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-09 17:27:40 +08:00
|
|
|
|
return orderNo
|
2024-11-21 12:14:34 +08:00
|
|
|
|
}
|