qnc-server-tob/app/main/api/internal/service/alipayService.go

259 lines
7.8 KiB
Go
Raw Normal View History

2025-01-10 00:09:25 +08:00
package service
import (
"context"
2025-04-08 14:50:24 +08:00
"crypto/rand"
"encoding/hex"
2025-01-10 00:09:25 +08:00
"fmt"
"net/http"
2025-06-09 12:34:52 +08:00
"qnc-server/app/main/api/internal/config"
"qnc-server/app/main/model"
2025-04-11 13:10:17 +08:00
"qnc-server/pkg/lzkit/lzUtils"
2025-01-10 00:09:25 +08:00
"strconv"
2025-04-08 14:50:24 +08:00
"sync/atomic"
2025-01-10 00:09:25 +08:00
"time"
2025-03-17 15:59:09 +08:00
"github.com/smartwalle/alipay/v3"
2025-01-10 00:09:25 +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))
}
2025-03-07 03:48:59 +08:00
//// 加载支付宝公钥
//err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey)
//if err != nil {
// panic(fmt.Sprintf("加载支付宝公钥失败: %v", err))
//}
// 加载证书
if err = client.LoadAppCertPublicKeyFromFile(c.Alipay.AppCertPath); err != nil {
panic(fmt.Sprintf("加载应用公钥证书失败: %v", err))
}
if err = client.LoadAlipayCertPublicKeyFromFile(c.Alipay.AlipayCertPath); err != nil {
panic(fmt.Sprintf("加载支付宝公钥证书失败: %v", err))
2025-01-10 00:09:25 +08:00
}
2025-03-07 03:48:59 +08:00
if err = client.LoadAliPayRootCertFromFile(c.Alipay.AlipayRootCertPath); err != nil {
panic(fmt.Sprintf("加载根证书失败: %v", err))
}
2025-01-10 00:09:25 +08:00
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支付订单
2025-04-15 22:52:02 +08:00
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string) (string, error) {
2025-01-10 00:09:25 +08:00
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: a.config.NotifyUrl, // 异步回调通知地址
2025-04-15 22:52:02 +08:00
ReturnURL: a.config.ReturnURL,
2025-01-10 00:09:25 +08:00
},
}
// 获取H5支付请求字符串这里会签名
payUrl, err := client.TradeWapPay(p)
if err != nil {
return "", fmt.Errorf("创建支付宝H5订单失败: %v", err)
}
return payUrl.String(), nil
}
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
2025-04-15 22:52:02 +08:00
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) {
2025-01-10 00:09:25 +08:00
// 根据 ctx 中的 platform 判断平台
platform, platformOk := ctx.Value("platform").(string)
if !platformOk {
return "", fmt.Errorf("支付平台不存在: %s", platform)
2025-01-10 00:09:25 +08:00
}
switch platform {
case model.PlatformApp:
2025-01-10 00:09:25 +08:00
// 调用App支付的创建方法
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
case model.PlatformH5:
2025-01-10 00:09:25 +08:00
// 调用H5支付的创建方法并传入 returnUrl
2025-04-15 22:52:02 +08:00
return a.CreateAlipayH5Order(amount, subject, outTradeNo)
2025-01-10 00:09:25 +08:00
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)
}
2025-04-08 14:50:24 +08:00
// 添加全局原子计数器
var alipayOrderCounter uint32 = 0
// GenerateOutTradeNo 生成唯一订单号的函数 - 优化版本
2025-01-10 00:09:25 +08:00
func (a *AliPayService) GenerateOutTradeNo() string {
2025-04-08 14:50:24 +08:00
// 获取当前时间戳(毫秒级)
timestamp := time.Now().UnixMilli()
2025-01-10 00:09:25 +08:00
timeStr := strconv.FormatInt(timestamp, 10)
2025-04-08 14:50:24 +08:00
// 原子递增计数器
counter := atomic.AddUint32(&alipayOrderCounter, 1)
2025-01-10 00:09:25 +08:00
2025-04-08 14:50:24 +08:00
// 生成4字节真随机数
randomBytes := make([]byte, 4)
_, err := rand.Read(randomBytes)
if err != nil {
// 如果随机数生成失败,回退到使用时间纳秒数据
randomBytes = []byte(strconv.FormatInt(time.Now().UnixNano()%1000000, 16))
2025-01-10 00:09:25 +08:00
}
2025-04-08 14:50:24 +08:00
randomHex := hex.EncodeToString(randomBytes)
// 组合所有部分: 前缀 + 时间戳 + 计数器 + 随机数
orderNo := fmt.Sprintf("%s%06x%s", timeStr[:10], counter%0xFFFFFF, randomHex[:6])
2025-01-10 00:09:25 +08:00
2025-04-08 14:50:24 +08:00
// 确保长度不超过32字符大多数支付平台的限制
if len(orderNo) > 32 {
orderNo = orderNo[:32]
2025-01-10 00:09:25 +08:00
}
2025-04-08 14:50:24 +08:00
return orderNo
2025-01-10 00:09:25 +08:00
}
2025-03-07 03:48:59 +08:00
// AliTransfer 支付宝单笔转账到支付宝账户(提现功能)
func (a *AliPayService) AliTransfer(
ctx context.Context,
payeeAccount string, // 收款方支付宝账户
payeeName string, // 收款方姓名
amount float64, // 转账金额
remark string, // 转账备注
outBizNo string, // 商户转账唯一订单号可使用GenerateOutTradeNo生成
) (*alipay.FundTransUniTransferRsp, error) {
// 参数校验
if payeeAccount == "" {
return nil, fmt.Errorf("收款账户不能为空")
}
if amount <= 0 {
return nil, fmt.Errorf("转账金额必须大于0")
}
// 构造转账请求
req := alipay.FundTransUniTransfer{
OutBizNo: outBizNo,
TransAmount: lzUtils.ToAlipayAmount(amount), // 金额格式转换
ProductCode: "TRANS_ACCOUNT_NO_PWD", // 单笔无密转账到支付宝账户
BizScene: "DIRECT_TRANSFER", // 单笔转账
OrderTitle: "账户提现", // 转账标题
Remark: remark,
PayeeInfo: &alipay.PayeeInfo{
Identity: payeeAccount,
IdentityType: "ALIPAY_LOGON_ID", // 根据账户类型选择:
Name: payeeName,
// ALIPAY_USER_ID/ALIPAY_LOGON_ID
},
}
// 执行转账请求
transferRsp, err := a.AlipayClient.FundTransUniTransfer(ctx, req)
if err != nil {
return nil, fmt.Errorf("支付宝转账请求失败: %v", err)
}
return transferRsp, nil
}
func (a *AliPayService) QueryTransferStatus(
ctx context.Context,
outBizNo string,
) (*alipay.FundTransOrderQueryRsp, error) {
req := alipay.FundTransOrderQuery{
OutBizNo: outBizNo,
}
response, err := a.AlipayClient.FundTransOrderQuery(ctx, req)
if err != nil {
return nil, fmt.Errorf("支付宝接口调用失败: %v", err)
}
// 处理响应
if response.Code.IsFailure() {
return nil, fmt.Errorf("支付宝返回错误: %s-%s", response.Code, response.Msg)
}
return response, nil
}