259 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			259 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package service | |||
|  | 
 | |||
|  | import ( | |||
|  | 	"aedata-server/app/main/api/internal/config" | |||
|  | 	"aedata-server/app/main/model" | |||
|  | 	"aedata-server/pkg/lzkit/lzUtils" | |||
|  | 	"context" | |||
|  | 	"crypto/rand" | |||
|  | 	"encoding/hex" | |||
|  | 	"fmt" | |||
|  | 	"net/http" | |||
|  | 	"strconv" | |||
|  | 	"sync/atomic" | |||
|  | 	"time" | |||
|  | 
 | |||
|  | 	"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)) | |||
|  | 	//} | |||
|  | 
 | |||
|  | 	// 加载证书 | |||
|  | 	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)) | |||
|  | 	} | |||
|  | 	if err = client.LoadAliPayRootCertFromFile(c.Alipay.AlipayRootCertPath); 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) (string, error) { | |||
|  | 	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, // 异步回调通知地址 | |||
|  | 			ReturnURL:   a.config.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) (string, error) { | |||
|  | 	// 根据 ctx 中的 platform 判断平台 | |||
|  | 	platform, platformOk := ctx.Value("platform").(string) | |||
|  | 	if !platformOk { | |||
|  | 		return "", fmt.Errorf("无的支付平台: %s", platform) | |||
|  | 	} | |||
|  | 	switch platform { | |||
|  | 	case model.PlatformApp: | |||
|  | 		// 调用App支付的创建方法 | |||
|  | 		return a.CreateAlipayAppOrder(amount, subject, outTradeNo) | |||
|  | 	case model.PlatformH5: | |||
|  | 		// 调用H5支付的创建方法,并传入 returnUrl | |||
|  | 		return a.CreateAlipayH5Order(amount, subject, outTradeNo) | |||
|  | 	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("refund-%s", 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 | |||
|  | } | |||
|  | 
 | |||
|  | // 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 | |||
|  | } |