260 lines
7.7 KiB
Go
260 lines
7.7 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"github.com/smartwalle/alipay/v3"
|
||
mathrand "math/rand"
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
"tydata-server/app/user/cmd/api/internal/config"
|
||
"tydata-server/pkg/lzkit/lzUtils"
|
||
)
|
||
|
||
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, brand string) (string, error) {
|
||
var returnURL string
|
||
if brand == "tyc" {
|
||
returnURL = "https://www.tianyuancha.com/report"
|
||
} else if brand == "qnc" {
|
||
returnURL = "https://www.quannengcha.com/report"
|
||
} else {
|
||
returnURL = a.config.ReturnURL
|
||
}
|
||
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: 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)
|
||
}
|
||
|
||
// GenerateOutTradeNo 生成唯一订单号的函数
|
||
func (a *AliPayService) GenerateOutTradeNo() string {
|
||
length := 16
|
||
// 获取当前时间戳
|
||
timestamp := time.Now().UnixNano()
|
||
|
||
// 转换为字符串
|
||
timeStr := strconv.FormatInt(timestamp, 10)
|
||
|
||
// 生成随机数
|
||
mathrand.Seed(time.Now().UnixNano())
|
||
randomPart := strconv.Itoa(mathrand.Intn(1000000))
|
||
|
||
// 组合时间戳和随机数
|
||
combined := timeStr + randomPart
|
||
|
||
// 如果长度超出指定值,则截断;如果不够,则填充随机字符
|
||
if len(combined) >= length {
|
||
return combined[:length]
|
||
}
|
||
|
||
// 如果长度不够,填充0
|
||
for len(combined) < length {
|
||
combined += strconv.Itoa(mathrand.Intn(10)) // 填充随机数
|
||
}
|
||
|
||
return combined
|
||
}
|
||
|
||
// 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
|
||
}
|