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 }