fix: 支付回调补写 actual_paid_amount 字段,从支付平台回调中提取实付金额
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m48s
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:
@@ -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):
|
||||
|
||||
@@ -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, "更新订单支付状态失败")
|
||||
|
||||
Reference in New Issue
Block a user