From 0a2961ecd6e3886ac260ccf727d96bd5b2f4f1d1 Mon Sep 17 00:00:00 2001 From: huang Date: Fri, 17 Apr 2026 11:12:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E4=BB=98=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E8=A1=A5=E5=86=99=20actual=5Fpaid=5Famount=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=8C=E4=BB=8E=E6=94=AF=E4=BB=98=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E4=B8=AD=E6=8F=90=E5=8F=96=E5=AE=9E=E4=BB=98?= =?UTF-8?q?=E9=87=91=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HandlePaymentCallback 新增 actualPaidAmount int64 参数,Updates 写入 actual_paid_amount - 微信 V2 回调从 TotalFee 字符串解析分值;V3 直接使用 TotalAmount - 支付宝回调优先取 buyer_pay_amount(实付),回退 total_amount,元转分使用 math.Round - 富友回调从 OrderAmt 字符串解析分值 - dispatchWechatCallback 同步增加 paidAmount 参数透传 --- internal/handler/callback/payment.go | 43 +++++++++++++++++++++++----- internal/service/order/service.go | 11 +++---- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/internal/handler/callback/payment.go b/internal/handler/callback/payment.go index e44c404..e3508b0 100644 --- a/internal/handler/callback/payment.go +++ b/internal/handler/callback/payment.go @@ -3,7 +3,9 @@ package callback import ( "context" "fmt" + "math" "net/http" + "strconv" "strings" "github.com/gofiber/fiber/v2" @@ -97,7 +99,9 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error { if result.TradeState != "SUCCESS" { return h.wechatV2SuccessResponse(c) } - if err := h.dispatchWechatCallback(ctx, result.OutTradeNo, result.TransactionID); err != nil { + // TotalFee 为字符串格式的分,解析失败则降级为 0(后续 handlePaymentCallback 会记录日志) + totalFee, _ := strconv.ParseInt(result.TotalFee, 10, 64) + if err := h.dispatchWechatCallback(ctx, result.OutTradeNo, result.TransactionID, totalFee); err != nil { return errors.Wrap(errors.CodeWechatCallbackInvalid, err, "处理微信支付回调失败") } @@ -111,7 +115,7 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error { if result.TradeState != "SUCCESS" { return nil } - return h.dispatchWechatCallback(ctx, result.OutTradeNo, result.TransactionID) + return h.dispatchWechatCallback(ctx, result.OutTradeNo, result.TransactionID, result.TotalAmount) }) if err != nil { return errors.Wrap(errors.CodeWechatCallbackInvalid, err, "处理微信支付回调失败") @@ -125,10 +129,10 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error { } // dispatchWechatCallback 按订单号前缀将支付结果分发到对应业务 -func (h *PaymentHandler) dispatchWechatCallback(ctx context.Context, outTradeNo, transactionID string) error { +func (h *PaymentHandler) dispatchWechatCallback(ctx context.Context, outTradeNo, transactionID string, paidAmount int64) error { switch { case strings.HasPrefix(outTradeNo, "ORD"): - return h.orderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat) + return h.orderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat, paidAmount) case strings.HasPrefix(outTradeNo, constants.AssetRechargeOrderPrefix): if h.rechargeOrderService != nil { return h.rechargeOrderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentByWechat, transactionID) @@ -151,8 +155,24 @@ func (h *PaymentHandler) wechatV2SuccessResponse(c *fiber.Ctx) error { return c.SendString(``) } +// parseYuanToFen 将支付宝元为单位的金额字符串转换为分 +// 支付宝回调金额字段(buyer_pay_amount / total_amount)为字符串格式的元,如 "2.00" +// 使用 math.Round 避免浮点运算精度误差 +func parseYuanToFen(yuan string) int64 { + if yuan == "" { + return 0 + } + f, err := strconv.ParseFloat(yuan, 64) + if err != nil { + return 0 + } + return int64(math.Round(f * 100)) +} + type AlipayCallbackRequest struct { - OrderNo string `json:"out_trade_no" form:"out_trade_no"` + OrderNo string `json:"out_trade_no" form:"out_trade_no"` + BuyerPayAmount string `json:"buyer_pay_amount" form:"buyer_pay_amount"` + TotalAmount string `json:"total_amount" form:"total_amount"` } // AlipayCallback 支付宝回调 @@ -169,10 +189,17 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error { ctx := c.UserContext() + // 优先取 buyer_pay_amount(买家实付,扣除优惠券后),回退到 total_amount + rawAmount := req.BuyerPayAmount + if rawAmount == "" { + rawAmount = req.TotalAmount + } + paidAmount := parseYuanToFen(rawAmount) + // 按订单号前缀分发 switch { case strings.HasPrefix(req.OrderNo, "ORD"): - if err := h.orderService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay); err != nil { + if err := h.orderService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay, paidAmount); err != nil { return err } case strings.HasPrefix(req.OrderNo, constants.AssetRechargeOrderPrefix): @@ -248,9 +275,11 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error { } orderNo := notify.MchntOrderNo + // OrderAmt 为字符串格式的分,解析失败则降级为 0 + orderAmt, _ := strconv.ParseInt(notify.OrderAmt, 10, 64) switch { case strings.HasPrefix(orderNo, "ORD"): - if err := h.orderService.HandlePaymentCallback(ctx, orderNo, "fuiou"); err != nil { + if err := h.orderService.HandlePaymentCallback(ctx, orderNo, "fuiou", orderAmt); err != nil { return c.Send(fuiou.BuildNotifyFailResponse(err.Error())) } case strings.HasPrefix(orderNo, constants.AssetRechargeOrderPrefix): diff --git a/internal/service/order/service.go b/internal/service/order/service.go index f6456d2..83c978a 100644 --- a/internal/service/order/service.go +++ b/internal/service/order/service.go @@ -1410,7 +1410,7 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string, return nil } -func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, paymentMethod string) error { +func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, paymentMethod string, actualPaidAmount int64) error { order, err := s.orderStore.GetByOrderNo(ctx, orderNo) if err != nil { if err == gorm.ErrRecordNotFound { @@ -1424,10 +1424,11 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay result := tx.Model(&model.Order{}). Where("id = ? AND payment_status = ?", order.ID, model.PaymentStatusPending). Updates(map[string]any{ - "payment_status": model.PaymentStatusPaid, - "payment_method": paymentMethod, - "paid_at": now, - "expires_at": nil, // 支付成功,清除过期时间 + "payment_status": model.PaymentStatusPaid, + "payment_method": paymentMethod, + "paid_at": now, + "expires_at": nil, // 支付成功,清除过期时间 + "actual_paid_amount": actualPaidAmount, }) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败")