fix: 支付回调补写 actual_paid_amount 字段,从支付平台回调中提取实付金额
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m48s

- HandlePaymentCallback 新增 actualPaidAmount int64 参数,Updates 写入 actual_paid_amount
- 微信 V2 回调从 TotalFee 字符串解析分值;V3 直接使用 TotalAmount
- 支付宝回调优先取 buyer_pay_amount(实付),回退 total_amount,元转分使用 math.Round
- 富友回调从 OrderAmt 字符串解析分值
- dispatchWechatCallback 同步增加 paidAmount 参数透传
This commit is contained in:
2026-04-17 11:12:35 +08:00
parent fa554c3930
commit 0a2961ecd6
2 changed files with 42 additions and 12 deletions

View File

@@ -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(`<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`)
}
// 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):

View File

@@ -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, "更新订单支付状态失败")