This commit is contained in:
2026-03-04 20:07:41 +08:00
parent 6e3c268b82
commit 66d8d660f2
11 changed files with 272 additions and 89 deletions

View File

@@ -82,7 +82,8 @@ func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *type
orderPayTime = &order.PayTime.Time
}
refundNo := l.generateRefundNo(order.OrderNo)
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount, orderPayTime, refundNo)
// 按订单记录的商户号 payment_merchant 选择支付宝商户;老订单未写入时由 AliRefund 内部按时间区间兜底。
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.PaymentMerchant, order.OrderNo, req.RefundAmount, orderPayTime, refundNo)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err)
}
@@ -146,6 +147,7 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
PaymentMerchant: order.PaymentMerchant,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, // 使用传入的状态,不再硬编码
@@ -174,6 +176,7 @@ func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
PaymentMerchant: order.PaymentMerchant,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus,

View File

@@ -32,22 +32,58 @@ func NewAlipayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Al
}
func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Request) error {
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(r)
if err != nil {
logx.Errorf("支付宝支付回调,%v", err)
// 先解析表单,拿到 out_trade_no 用于查找订单和对应商户号
if err := r.ParseForm(); err != nil {
logx.Errorf("支付宝支付回调,解析请求表单失败: %v", err)
return nil
}
// 根据订单号前缀判断订单类型
orderNo := notification.OutTradeNo
orderNo := r.FormValue("out_trade_no")
if orderNo == "" {
logx.Errorf("支付宝支付回调,缺少 out_trade_no")
return nil
}
// 根据订单号前缀判断订单类型,并查出对应商户标识
var merchant string
if strings.HasPrefix(orderNo, "Q_") {
// 查询订单处理
return l.handleQueryOrderPayment(w, notification)
// 查询订单
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("支付宝支付回调,查询订单失败: %v", err)
return nil
}
merchant = order.PaymentMerchant
} else if strings.HasPrefix(orderNo, "A_") {
// 代理会员订单处理
return l.handleAgentVipOrderPayment(w, notification)
// 代理会员订单
agentOrder, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("支付宝支付回调,查询代理会员订单失败: %v", err)
return nil
}
merchant = agentOrder.PaymentMerchant
} else {
// 兼容旧订单,假设没有前缀的是查询订单
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("支付宝支付回调(旧订单),查询订单失败: %v", err)
return nil
}
merchant = order.PaymentMerchant
}
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(merchant, r.Form)
if err != nil {
logx.Errorf("支付宝支付回调,验签失败: %v", err)
return nil
}
// 再次根据订单号前缀分发到具体处理函数
if strings.HasPrefix(orderNo, "Q_") {
return l.handleQueryOrderPayment(w, notification)
} else if strings.HasPrefix(orderNo, "A_") {
return l.handleAgentVipOrderPayment(w, notification)
} else {
return l.handleQueryOrderPayment(w, notification)
}
}
@@ -218,9 +254,10 @@ func (l *AlipayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeO
return refundErr
}
} else {
// 代理会员订单以创建时间为准,仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户
// 支付宝退款按订单记录的商户号 payment_merchant 走对应商户
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := order.CreateTime
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount, &orderPayTime, "")
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, &orderPayTime, "")
if refundErr != nil {
return refundErr
}

View File

@@ -28,10 +28,11 @@ type PaymentLogic struct {
svcCtx *svc.ServiceContext
}
type PaymentTypeResp struct {
amount float64
outTradeNo string
description string
orderID int64
amount float64
outTradeNo string
description string
orderID int64
payMerchantID string
}
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
@@ -79,7 +80,8 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
if req.PayMethod == "wechat" {
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "alipay" {
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
// 支付宝按订单写入的商户标识one/two创建支付订单
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.payMerchantID, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "appleiap" {
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
}
@@ -186,12 +188,24 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
amount = 0.01
}
var orderID int64
// 默认支付宝商户号为 one若为代理推广订单存在 AgentIdentifier则使用 two。
paymentMerchant := ""
if req.PayMethod == "alipay" {
if data.AgentIdentifier != "" {
paymentMerchant = "two"
} else {
paymentMerchant = "one"
}
}
order := model.Order{
OrderNo: outTradeNo,
UserId: userID,
ProductId: product.Id,
PaymentPlatform: req.PayMethod,
PaymentScene: "app",
PaymentMerchant: paymentMerchant,
Amount: amount,
Status: "pending",
}
@@ -228,7 +242,13 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
}
}
return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil
return &PaymentTypeResp{
amount: amount,
outTradeNo: outTradeNo,
description: product.ProductName,
orderID: orderID,
payMerchantID: paymentMerchant,
}, nil
}
func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
@@ -274,20 +294,32 @@ func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.
if user.Inside == 1 {
amount = 0.01
}
paymentMerchant := ""
if req.PayMethod == "alipay" {
paymentMerchant = "one"
}
agentMembershipRechargeOrder := model.AgentMembershipRechargeOrder{
OrderNo: req.Id,
UserId: userID,
AgentId: agentModel.Id,
Amount: amount,
PaymentMethod: req.PayMethod,
LevelName: agentVipCache.Type,
Status: "pending",
OrderNo: req.Id,
UserId: userID,
AgentId: agentModel.Id,
Amount: amount,
PaymentMethod: req.PayMethod,
PaymentMerchant: paymentMerchant,
LevelName: agentVipCache.Type,
Status: "pending",
}
_, err = l.svcCtx.AgentMembershipRechargeOrderModel.Insert(l.ctx, session, &agentMembershipRechargeOrder)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理会员充值订单失败: %+v", err)
}
return &PaymentTypeResp{amount: amount, outTradeNo: req.Id, description: fmt.Sprintf("%s代理会员充值", agentMembershipConfig.LevelName)}, nil
return &PaymentTypeResp{
amount: amount,
outTradeNo: req.Id,
description: fmt.Sprintf("%s代理会员充值", agentMembershipConfig.LevelName),
payMerchantID: paymentMerchant,
}, nil
}
func (l *PaymentLogic) agentParsing(agentIdentifier string) (*types.AgentIdentifier, error) {
key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10")

View File

@@ -221,9 +221,10 @@ func (l *WechatPayCallbackLogic) handleRefund(order *model.AgentMembershipRechar
return refundErr
}
} else {
// 代理会员订单以创建时间为准,仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户
// 支付宝退款按订单记录的商户号 payment_merchant 走对应商户
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := order.CreateTime
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount, &orderPayTime, "")
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, &orderPayTime, "")
if refundErr != nil {
return refundErr
}

View File

@@ -322,12 +322,13 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
logx.Infof("已发起微信退款申请, orderID: %d, amount: %f", order.Id, order.Amount)
return asynq.SkipRetry
} else {
// 支付宝退款为同步结果,仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户
// 支付宝退款为同步结果,优先按订单记录的 payment_merchant 选择商户
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := &order.CreateTime
if order.PayTime.Valid {
orderPayTime = &order.PayTime.Time
}
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount, orderPayTime, "")
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, orderPayTime, "")
if refundErr != nil {
logx.Error(refundErr)
return asynq.SkipRetry

View File

@@ -5,7 +5,7 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"time"
@@ -28,6 +28,28 @@ type AliPayService struct {
AlipayClientBak *alipay.Client // 仅用于 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单的退款
}
// clientForMerchant 根据商户标识与订单时间选择对应的支付宝 client。
// - merchant == "two" 且 Bak 存在:优先返回 Bak
// - merchant == "one" 或空:默认返回主商户;
// - merchant 为空且 orderPayTime 落在备份时间区间:兼容老订单,走 Bak。
func (a *AliPayService) clientForMerchant(merchant string, orderPayTime *time.Time) *alipay.Client {
// 显式指定 two则优先走 Bak
if merchant == "two" && a.AlipayClientBak != nil {
return a.AlipayClientBak
}
// 显式指定 one 或其他未知标识,一律走主商户
if merchant == "one" || merchant == "" {
// 对于老订单未写入 merchant 的情况,继续保留时间区间兜底逻辑
if merchant == "" && orderPayTime != nil && a.AlipayClientBak != nil &&
!orderPayTime.Before(AlipayBakRefundStart) && orderPayTime.Before(AlipayBakRefundEnd) {
return a.AlipayClientBak
}
return a.AlipayClient
}
// 兜底:未知标识时仍走主商户,避免因为配置问题导致整体不可用
return a.AlipayClient
}
// NewAliPayService 是一个构造函数,用于初始化 AliPayService
func NewAliPayService(c config.Config) *AliPayService {
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
@@ -77,8 +99,8 @@ func NewAliPayService(c config.Config) *AliPayService {
return svc
}
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) {
client := a.AlipayClient
func (a *AliPayService) CreateAlipayAppOrder(merchant string, amount float64, subject string, outTradeNo string) (string, error) {
client := a.clientForMerchant(merchant, nil)
totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造移动支付请求
p := alipay.TradeAppPay{
@@ -101,8 +123,8 @@ func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, out
}
// CreateAlipayH5Order 创建支付宝H5支付订单
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string) (string, error) {
client := a.AlipayClient
func (a *AliPayService) CreateAlipayH5Order(merchant string, amount float64, subject string, outTradeNo string) (string, error) {
client := a.clientForMerchant(merchant, nil)
totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造H5支付请求
p := alipay.TradeWapPay{
@@ -124,8 +146,9 @@ func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outT
return payUrl.String(), nil
}
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) {
// CreateAlipayOrder 根据平台类型和商户标识创建支付宝支付订单
// merchant: 商户标识,目前约定 "one"=主商户, "two"=备商户
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, merchant string, amount float64, subject string, outTradeNo string) (string, error) {
// 根据 ctx 中的 platform 判断平台
platform, platformOk := ctx.Value("platform").(string)
if !platformOk {
@@ -134,23 +157,21 @@ func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, s
switch platform {
case model.PlatformApp:
// 调用App支付的创建方法
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
return a.CreateAlipayAppOrder(merchant, amount, subject, outTradeNo)
case model.PlatformH5:
// 调用H5支付的创建方法并传入 returnUrl
return a.CreateAlipayH5Order(amount, subject, outTradeNo)
return a.CreateAlipayH5Order(merchant, amount, subject, outTradeNo)
default:
return "", fmt.Errorf("不支持的支付平台: %s", platform)
}
}
// AliRefund 发起支付宝退款。orderPayTime 为订单支付时间(或创建时间);仅当落在 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内时使用 bak 商户号,否则使用正式商户号;传 nil 则使用正式商户号。
// AliRefund 发起支付宝退款。
// merchant: 支付商户标识one/two。为空时按老逻辑仅在备份时间区间内使用 Bak。
// orderPayTime 为订单支付时间(或创建时间);用于老订单按时间区间选择商户;传 nil 则忽略时间区间。
// outRequestNo 为商户退款请求号,同一笔退款需唯一;传空则使用 "refund-"+outTradeNo重试时建议传入唯一号避免支付宝报重复。
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64, orderPayTime *time.Time, outRequestNo string) (*alipay.TradeRefundRsp, error) {
client := a.AlipayClient
if orderPayTime != nil && a.AlipayClientBak != nil &&
!orderPayTime.Before(AlipayBakRefundStart) && orderPayTime.Before(AlipayBakRefundEnd) {
client = a.AlipayClientBak
}
func (a *AliPayService) AliRefund(ctx context.Context, merchant string, outTradeNo string, refundAmount float64, orderPayTime *time.Time, outRequestNo string) (*alipay.TradeRefundRsp, error) {
client := a.clientForMerchant(merchant, orderPayTime)
if outRequestNo == "" {
outRequestNo = fmt.Sprintf("refund-%s", outTradeNo)
@@ -168,27 +189,26 @@ func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refund
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)
}
// HandleAliPaymentNotification 支付宝支付回调验签。
// 由上层根据 out_trade_no 查出订单并传入对应商户标识 merchant。
func (a *AliPayService) HandleAliPaymentNotification(merchant string, form url.Values) (*alipay.Notification, error) {
client := a.clientForMerchant(merchant, nil)
// 解析并验证通知DecodeNotification 会自动验证签名
notification, err := a.AlipayClient.DecodeNotification(r.Form)
notification, err := client.DecodeNotification(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) {
// QueryOrderStatus 按商户标识查询支付宝订单状态
func (a *AliPayService) QueryOrderStatus(ctx context.Context, merchant string, outTradeNo string) (*alipay.TradeQueryRsp, error) {
client := a.clientForMerchant(merchant, nil)
queryRequest := alipay.TradeQuery{
OutTradeNo: outTradeNo,
}
// 发起查询请求
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest)
resp, err := client.TradeQuery(ctx, queryRequest)
if err != nil {
return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
}