From b4ee983d1d8c77f0b82878b954cac1eff861a2c6 Mon Sep 17 00:00:00 2001 From: zizi Date: Wed, 20 May 2026 15:49:08 +0800 Subject: [PATCH] fix: validate direct payment callback amounts Check direct payment callback currency, provider, and paid amount before settling Alipay or WxPay top-ups. --- controller/payment_alipay.go | 5 +++++ controller/payment_wxpay.go | 5 +++++ model/payment_method_guard_test.go | 22 ++++++++++++++++++++++ model/topup.go | 24 ++++++++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/controller/payment_alipay.go b/controller/payment_alipay.go index 5e1d9ad7..35baf45a 100644 --- a/controller/payment_alipay.go +++ b/controller/payment_alipay.go @@ -179,6 +179,11 @@ func AlipayNotify(c *gin.Context) { LockOrder(status.ProviderOrderID) defer UnlockOrder(status.ProviderOrderID) + if err := model.ValidateTopUpPaymentAmount(status.ProviderOrderID, model.PaymentProviderAlipay, status.Amount, status.Currency); err != nil { + logger.LogWarn(c.Request.Context(), fmt.Sprintf("支付宝 异步通知金额校验失败 trade_no=%s amount=%d currency=%s client_ip=%s error=%q", status.ProviderOrderID, status.Amount, status.Currency, c.ClientIP(), err.Error())) + c.String(http.StatusOK, "fail") + return + } if err := model.RechargeAlipay(status.ProviderOrderID, c.ClientIP()); err != nil { logger.LogError(c.Request.Context(), fmt.Sprintf("支付宝 充值处理失败 trade_no=%s client_ip=%s error=%q", status.ProviderOrderID, c.ClientIP(), err.Error())) c.String(http.StatusOK, "fail") diff --git a/controller/payment_wxpay.go b/controller/payment_wxpay.go index 23d3a183..bd89b9cc 100644 --- a/controller/payment_wxpay.go +++ b/controller/payment_wxpay.go @@ -164,6 +164,11 @@ func WxpayNotify(c *gin.Context) { LockOrder(status.ProviderOrderID) defer UnlockOrder(status.ProviderOrderID) + if err := model.ValidateTopUpPaymentAmount(status.ProviderOrderID, model.PaymentProviderWxpay, status.Amount, status.Currency); err != nil { + logger.LogWarn(c.Request.Context(), fmt.Sprintf("微信支付 异步通知金额校验失败 trade_no=%s amount=%d currency=%s client_ip=%s error=%q", status.ProviderOrderID, status.Amount, status.Currency, c.ClientIP(), err.Error())) + wxpayNotifyFail(c, "金额校验失败") + return + } if err := model.RechargeWxpay(status.ProviderOrderID, c.ClientIP()); err != nil { logger.LogError(c.Request.Context(), fmt.Sprintf("微信支付 充值处理失败 trade_no=%s client_ip=%s error=%q", status.ProviderOrderID, c.ClientIP(), err.Error())) wxpayNotifyFail(c, "处理失败") diff --git a/model/payment_method_guard_test.go b/model/payment_method_guard_test.go index 59a8ec25..f531d8d6 100644 --- a/model/payment_method_guard_test.go +++ b/model/payment_method_guard_test.go @@ -322,6 +322,28 @@ func TestRechargeWaffo_ProcessesAffiliateRebateIdempotently(t *testing.T) { assert.Equal(t, int64(1), count) } +func TestValidateTopUpPaymentAmount(t *testing.T) { + truncateTables(t) + + insertUserForPaymentGuardTest(t, 186, 0) + topUp := &TopUp{ + UserId: 186, + Amount: 2, + Money: 12.34, + TradeNo: "direct-pay-amount-guard", + PaymentMethod: PaymentMethodAlipay, + PaymentProvider: PaymentProviderAlipay, + Status: common.TopUpStatusPending, + CreateTime: time.Now().Unix(), + } + require.NoError(t, topUp.Insert()) + + require.NoError(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderAlipay, 1234, "CNY")) + require.ErrorIs(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderAlipay, 1233, "CNY"), ErrTopUpAmountMismatch) + require.ErrorIs(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderAlipay, 1234, "USD"), ErrTopUpCurrencyMismatch) + require.ErrorIs(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderWxpay, 1234, "CNY"), ErrPaymentMethodMismatch) +} + func TestCompleteSubscriptionOrder_RejectsMismatchedPaymentProvider(t *testing.T) { truncateTables(t) diff --git a/model/topup.go b/model/topup.go index ada6985b..fe55edf7 100644 --- a/model/topup.go +++ b/model/topup.go @@ -52,6 +52,8 @@ var ( ErrPaymentMethodMismatch = errors.New("payment method mismatch") ErrTopUpNotFound = errors.New("topup not found") ErrTopUpStatusInvalid = errors.New("topup status invalid") + ErrTopUpAmountMismatch = errors.New("topup amount mismatch") + ErrTopUpCurrencyMismatch = errors.New("topup currency mismatch") ) type promoCodeBonusInfo struct { @@ -235,6 +237,28 @@ func UpdatePendingTopUpStatus(tradeNo string, expectedPaymentProvider string, ta }) } +func ValidateTopUpPaymentAmount(tradeNo string, expectedPaymentProvider string, paidAmountCents int, currency string) error { + if tradeNo == "" { + return ErrTopUpNotFound + } + if strings.ToUpper(strings.TrimSpace(currency)) != "CNY" { + return ErrTopUpCurrencyMismatch + } + + topUp := GetTopUpByTradeNo(tradeNo) + if topUp == nil { + return ErrTopUpNotFound + } + if expectedPaymentProvider != "" && topUp.PaymentProvider != expectedPaymentProvider { + return ErrPaymentMethodMismatch + } + expectedAmountCents := int(decimal.NewFromFloat(topUp.Money).Mul(decimal.NewFromInt(100)).Round(0).IntPart()) + if paidAmountCents != expectedAmountCents { + return ErrTopUpAmountMismatch + } + return nil +} + func RechargeEpay(tradeNo string, actualPaymentMethod string, callerIp string) (err error) { if tradeNo == "" { return errors.New("未提供支付单号")