tyc-server/pkg/core/payment/alipayService.go
2025-05-27 18:35:01 +08:00

294 lines
7.9 KiB
Go

package payment
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"tyc-server/pkg/lzkit/lzUtils"
"github.com/smartwalle/alipay/v3"
)
// AlipayConfig 支付宝配置
type AlipayConfig struct {
AppID string `json:"appId"` // 支付宝应用ID
PrivateKey string `json:"privateKey"` // 应用私钥
AlipayPublicKey string `json:"alipayPublicKey"` // 支付宝公钥
IsProduction bool `json:"isProduction"` // 是否生产环境
NotifyURL string `json:"notifyUrl"` // 支付结果通知地址
ReturnURL string `json:"returnUrl"` // 支付完成跳转地址
}
// AliPayService 支付宝支付服务
type AliPayService struct {
config AlipayConfig
alipayClient *alipay.Client
}
// NewAliPayService 创建支付宝支付服务
func NewAliPayService(c AlipayConfig) (*AliPayService, error) {
client, err := alipay.New(c.AppID, c.PrivateKey, c.IsProduction)
if err != nil {
return nil, fmt.Errorf("创建支付宝客户端失败: %w", err)
}
if err := client.LoadAliPayPublicKey(c.AlipayPublicKey); err != nil {
return nil, fmt.Errorf("加载支付宝公钥失败: %w", err)
}
return &AliPayService{
config: c,
alipayClient: client,
}, nil
}
// CreateOrder 创建支付宝支付订单
func (a *AliPayService) CreateOrder(ctx context.Context, req *AlipayCreateOrderRequest) (*AlipayCreateOrderResponse, error) {
if err := a.validateCreateOrderRequest(req); err != nil {
return nil, err
}
if req.NotifyURL == "" {
req.NotifyURL = a.config.NotifyURL
}
if req.ReturnURL == "" {
req.ReturnURL = a.config.ReturnURL
}
if req.OutTradeNo == "" {
req.OutTradeNo = a.GenerateOutTradeNo()
}
var payParams string
var err error
switch req.Platform {
case PlatformApp:
payParams, err = a.createAppOrder(req)
case PlatformH5:
payParams, err = a.createH5Order(req)
default:
return nil, errors.New("不支持的支付平台")
}
if err != nil {
return nil, err
}
return &AlipayCreateOrderResponse{
OrderNo: req.OutTradeNo,
PayParams: payParams,
}, nil
}
// createAppOrder 创建APP支付订单
func (a *AliPayService) createAppOrder(req *AlipayCreateOrderRequest) (string, error) {
p := alipay.TradeAppPay{
Trade: alipay.Trade{
Subject: req.Subject,
OutTradeNo: req.OutTradeNo,
TotalAmount: lzUtils.ToAlipayAmount(req.Amount),
ProductCode: "QUICK_MSECURITY_PAY",
NotifyURL: req.NotifyURL,
},
}
payStr, err := a.alipayClient.TradeAppPay(p)
if err != nil {
return "", fmt.Errorf("创建支付宝APP支付订单失败: %w", err)
}
return payStr, nil
}
// createH5Order 创建H5支付订单
func (a *AliPayService) createH5Order(req *AlipayCreateOrderRequest) (string, error) {
if req.ReturnURL == "" {
return "", errors.New("H5支付必须提供ReturnURL")
}
p := alipay.TradeWapPay{
Trade: alipay.Trade{
Subject: req.Subject,
OutTradeNo: req.OutTradeNo,
TotalAmount: lzUtils.ToAlipayAmount(req.Amount),
ProductCode: "QUICK_WAP_PAY",
NotifyURL: req.NotifyURL,
ReturnURL: req.ReturnURL,
},
}
payUrl, err := a.alipayClient.TradeWapPay(p)
if err != nil {
return "", fmt.Errorf("创建支付宝H5支付订单失败: %w", err)
}
return payUrl.String(), nil
}
// Refund 申请退款
func (a *AliPayService) Refund(ctx context.Context, req *AlipayRefundRequest) (*AlipayRefundResponse, error) {
if err := a.validateRefundRequest(req); err != nil {
return nil, err
}
if req.RefundNo == "" {
req.RefundNo = a.GenerateRefundNo()
}
refund := alipay.TradeRefund{
OutTradeNo: req.OrderNo,
RefundAmount: a.ToAlipayAmount(req.RefundAmount),
OutRequestNo: req.RefundNo,
}
resp, err := a.alipayClient.TradeRefund(ctx, refund)
if err != nil {
return nil, fmt.Errorf("申请支付宝退款失败: %w", err)
}
if !resp.IsSuccess() {
return nil, fmt.Errorf("申请支付宝退款失败: %s", resp.SubMsg)
}
return &AlipayRefundResponse{
OrderNo: req.OrderNo,
RefundNo: req.RefundNo,
RefundID: resp.TradeNo,
Status: StatusSuccess,
RefundTime: time.Now(),
RefundAmount: alipayamountToFloat(resp.RefundFee),
}, nil
}
// QueryOrder 查询订单
func (a *AliPayService) QueryOrder(ctx context.Context, req *QueryOrderRequest) (*QueryOrderResponse, error) {
if err := a.validateQueryRequest(req); err != nil {
return nil, err
}
query := alipay.TradeQuery{
OutTradeNo: req.OrderNo,
}
resp, err := a.alipayClient.TradeQuery(ctx, query)
if err != nil {
return nil, fmt.Errorf("查询支付宝订单失败: %w", err)
}
if !resp.IsSuccess() {
return nil, fmt.Errorf("查询支付宝订单失败: %s", resp.SubMsg)
}
amount, _ := strconv.ParseFloat(resp.TotalAmount, 64)
return &QueryOrderResponse{
OrderNo: resp.OutTradeNo,
TransactionID: resp.TradeNo,
Status: a.convertTradeStatus(resp.TradeStatus),
Amount: amount,
PayTime: a.parseAlipayTime(resp.SendPayDate),
PayMethod: MethodAlipay,
}, nil
}
// HandlePaymentNotification 处理支付结果通知
func (a *AliPayService) HandlePaymentNotification(req *http.Request) (*QueryOrderResponse, error) {
if err := req.ParseForm(); err != nil {
return nil, fmt.Errorf("解析支付宝通知失败: %w", err)
}
notification, err := a.alipayClient.DecodeNotification(req.Form)
if err != nil {
return nil, fmt.Errorf("验证支付宝通知签名失败: %w", err)
}
amount := alipayamountToFloat(notification.TotalAmount)
return &QueryOrderResponse{
OrderNo: notification.OutTradeNo,
TransactionID: notification.TradeNo,
Status: a.convertTradeStatus(alipay.TradeStatus(notification.TradeStatus)),
Amount: amount,
PayTime: a.parseAlipayTime(notification.GmtPayment),
PayMethod: MethodAlipay,
}, nil
}
// validateCreateOrderRequest 验证创建订单请求参数
func (a *AliPayService) validateCreateOrderRequest(req *AlipayCreateOrderRequest) error {
if req.Amount <= 0 {
return errors.New("支付金额必须大于0")
}
if req.Subject == "" {
return errors.New("商品描述不能为空")
}
if req.Platform == "" {
return errors.New("支付平台不能为空")
}
return nil
}
// validateRefundRequest 验证退款请求参数
func (a *AliPayService) validateRefundRequest(req *AlipayRefundRequest) error {
if req.OrderNo == "" {
return errors.New("商户订单号不能为空")
}
if req.RefundAmount <= 0 {
return errors.New("退款金额必须大于0")
}
return nil
}
// validateQueryRequest 验证查询请求参数
func (a *AliPayService) validateQueryRequest(req *QueryOrderRequest) error {
if req.OrderNo == "" && req.TransactionID == "" {
return errors.New("商户订单号和交易号不能同时为空")
}
return nil
}
// convertTradeStatus 转换支付宝交易状态
func (a *AliPayService) convertTradeStatus(status alipay.TradeStatus) PaymentStatus {
switch status {
case alipay.TradeStatusWaitBuyerPay:
return StatusPending
case alipay.TradeStatusSuccess:
return StatusSuccess
case alipay.TradeStatusClosed:
return StatusClosed
case alipay.TradeStatusFinished:
return StatusSuccess
default:
return StatusFailed
}
}
// parseAlipayTime 解析支付宝时间字符串
func (a *AliPayService) parseAlipayTime(timeStr string) time.Time {
t, err := time.Parse("2006-01-02 15:04:05", timeStr)
if err != nil {
return time.Now()
}
return t
}
// GenerateOutTradeNo 生成商户订单号
func (a *AliPayService) GenerateOutTradeNo() string {
return GenerateOrderNo(AlipayPrefix)
}
// GenerateRefundNo 生成退款订单号
func (a *AliPayService) GenerateRefundNo() string {
return GenerateOrderNo(fmt.Sprintf("%s_%s", RefundPrefix, AlipayPrefix))
}
// ToAlipayAmount 将金额从元转换为支付宝支付 SDK 需要的字符串格式,保留两位小数
func (a *AliPayService) ToAlipayAmount(amount float64) string {
// 格式化为字符串,保留两位小数
return fmt.Sprintf("%.2f", amount)
}
func alipayamountToFloat(amount string) float64 {
amountFloat, _ := strconv.ParseFloat(amount, 64)
return amountFloat
}