tyc-server/app/user/cmd/api/internal/service/alipayService.go
2025-04-09 17:27:40 +08:00

197 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"sync/atomic"
"time"
"tyc-server/app/user/cmd/api/internal/config"
"tyc-server/pkg/lzkit/lzUtils"
"github.com/smartwalle/alipay/v3"
)
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
}
// CreateAlipayH5Order 创建支付宝H5支付订单
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string, brand string) (string, error) {
var returnURL string
var notifyURL string
if brand == "tyc" {
returnURL = "https://www.tianyuancha.cn/report"
notifyURL = "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
} else {
returnURL = a.config.ReturnURL
notifyURL = a.config.NotifyUrl
}
client := a.AlipayClient
totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造H5支付请求
p := alipay.TradeWapPay{
Trade: alipay.Trade{
Subject: subject,
OutTradeNo: outTradeNo,
TotalAmount: totalAmount,
ProductCode: "QUICK_WAP_PAY", // H5支付专用产品码
NotifyURL: notifyURL, // 异步回调通知地址
ReturnURL: returnURL,
},
}
// 获取H5支付请求字符串这里会签名
payUrl, err := client.TradeWapPay(p)
if err != nil {
return "", fmt.Errorf("创建支付宝H5订单失败: %v", err)
}
return payUrl.String(), nil
}
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string, brand string) (string, error) {
// 根据 ctx 中的 platform 判断平台
platform, platformOk := ctx.Value("platform").(string)
if !platformOk {
return "", fmt.Errorf("无的支付平台: %s", platform)
}
switch platform {
case "app":
// 调用App支付的创建方法
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
case "h5":
// 调用H5支付的创建方法并传入 returnUrl
return a.CreateAlipayH5Order(amount, subject, outTradeNo, brand)
default:
return "", fmt.Errorf("不支持的支付平台: %s", platform)
}
}
// 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)
}
// 添加全局原子计数器
var alipayOrderCounter uint32 = 0
// GenerateOutTradeNo 生成唯一订单号的函数 - 优化版本
func (a *AliPayService) GenerateOutTradeNo() string {
// 获取当前时间戳(毫秒级)
timestamp := time.Now().UnixMilli()
timeStr := strconv.FormatInt(timestamp, 10)
// 原子递增计数器
counter := atomic.AddUint32(&alipayOrderCounter, 1)
// 生成4字节真随机数
randomBytes := make([]byte, 4)
_, err := rand.Read(randomBytes)
if err != nil {
// 如果随机数生成失败,回退到使用时间纳秒数据
randomBytes = []byte(strconv.FormatInt(time.Now().UnixNano()%1000000, 16))
}
randomHex := hex.EncodeToString(randomBytes)
// 组合所有部分: 前缀 + 时间戳 + 计数器 + 随机数
orderNo := fmt.Sprintf("%s%06x%s", timeStr[:10], counter%0xFFFFFF, randomHex[:6])
// 确保长度不超过32字符大多数支付平台的限制
if len(orderNo) > 32 {
orderNo = orderNo[:32]
}
return orderNo
}