Skip to content

Commit a7c38ec

Browse files
committed
fix: add PaymentProvider field to prevent cross-gateway callback attacks
EPay allows users to switch payment methods (e.g. wxpay→alipay) during checkout, causing callback rejection. Replace fragile blocklist guard with a PaymentProvider field on TopUp and SubscriptionOrder that identifies which gateway created the order.
1 parent 8993386 commit a7c38ec

12 files changed

Lines changed: 163 additions & 180 deletions

controller/subscription_payment_creem.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,14 @@ func SubscriptionRequestCreemPay(c *gin.Context) {
8383

8484
// create pending order first
8585
order := &model.SubscriptionOrder{
86-
UserId: userId,
87-
PlanId: plan.Id,
88-
Money: plan.PriceAmount,
89-
TradeNo: referenceId,
90-
PaymentMethod: model.PaymentMethodCreem,
91-
CreateTime: time.Now().Unix(),
92-
Status: common.TopUpStatusPending,
86+
UserId: userId,
87+
PlanId: plan.Id,
88+
Money: plan.PriceAmount,
89+
TradeNo: referenceId,
90+
PaymentMethod: model.PaymentMethodCreem,
91+
PaymentProvider: model.PaymentProviderCreem,
92+
CreateTime: time.Now().Unix(),
93+
Status: common.TopUpStatusPending,
9394
}
9495
if err := order.Insert(); err != nil {
9596
c.JSON(http.StatusOK, gin.H{"message": "error", "data": "创建订单失败"})

controller/subscription_payment_epay.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,14 @@ func SubscriptionRequestEpay(c *gin.Context) {
8282
}
8383

8484
order := &model.SubscriptionOrder{
85-
UserId: userId,
86-
PlanId: plan.Id,
87-
Money: plan.PriceAmount,
88-
TradeNo: tradeNo,
89-
PaymentMethod: req.PaymentMethod,
90-
CreateTime: time.Now().Unix(),
91-
Status: common.TopUpStatusPending,
85+
UserId: userId,
86+
PlanId: plan.Id,
87+
Money: plan.PriceAmount,
88+
TradeNo: tradeNo,
89+
PaymentMethod: req.PaymentMethod,
90+
PaymentProvider: model.PaymentProviderEpay,
91+
CreateTime: time.Now().Unix(),
92+
Status: common.TopUpStatusPending,
9293
}
9394
if err := order.Insert(); err != nil {
9495
common.ApiErrorMsg(c, "创建订单失败")
@@ -104,7 +105,7 @@ func SubscriptionRequestEpay(c *gin.Context) {
104105
ReturnUrl: returnUrl,
105106
})
106107
if err != nil {
107-
_ = model.ExpireSubscriptionOrder(tradeNo, req.PaymentMethod)
108+
_ = model.ExpireSubscriptionOrder(tradeNo, model.PaymentProviderEpay)
108109
common.ApiErrorMsg(c, "拉起支付失败")
109110
return
110111
}
@@ -156,7 +157,7 @@ func SubscriptionEpayNotify(c *gin.Context) {
156157
LockOrder(verifyInfo.ServiceTradeNo)
157158
defer UnlockOrder(verifyInfo.ServiceTradeNo)
158159

159-
if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo), verifyInfo.Type); err != nil {
160+
if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo), model.PaymentProviderEpay, verifyInfo.Type); err != nil {
160161
_, _ = c.Writer.Write([]byte("fail"))
161162
return
162163
}
@@ -205,7 +206,7 @@ func SubscriptionEpayReturn(c *gin.Context) {
205206
if verifyInfo.TradeStatus == epay.StatusTradeSuccess {
206207
LockOrder(verifyInfo.ServiceTradeNo)
207208
defer UnlockOrder(verifyInfo.ServiceTradeNo)
208-
if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo), verifyInfo.Type); err != nil {
209+
if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo), model.PaymentProviderEpay, verifyInfo.Type); err != nil {
209210
c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail")
210211
return
211212
}

controller/subscription_payment_stripe.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,14 @@ func SubscriptionRequestStripePay(c *gin.Context) {
8484
}
8585

8686
order := &model.SubscriptionOrder{
87-
UserId: userId,
88-
PlanId: plan.Id,
89-
Money: plan.PriceAmount,
90-
TradeNo: referenceId,
91-
PaymentMethod: model.PaymentMethodStripe,
92-
CreateTime: time.Now().Unix(),
93-
Status: common.TopUpStatusPending,
87+
UserId: userId,
88+
PlanId: plan.Id,
89+
Money: plan.PriceAmount,
90+
TradeNo: referenceId,
91+
PaymentMethod: model.PaymentMethodStripe,
92+
PaymentProvider: model.PaymentProviderStripe,
93+
CreateTime: time.Now().Unix(),
94+
Status: common.TopUpStatusPending,
9495
}
9596
if err := order.Insert(); err != nil {
9697
c.JSON(http.StatusOK, gin.H{"message": "error", "data": "创建订单失败"})

controller/topup.go

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,6 @@ type AmountRequest struct {
123123
Amount int64 `json:"amount"`
124124
}
125125

126-
var nonEpayPaymentMethodsForCallback = []string{
127-
model.PaymentMethodStripe,
128-
model.PaymentMethodCreem,
129-
model.PaymentMethodWaffo,
130-
model.PaymentMethodWaffoPancake,
131-
}
132-
133-
func isNonEpayPaymentMethodForEpayCallback(paymentMethod string) bool {
134-
return lo.Contains(nonEpayPaymentMethodsForCallback, paymentMethod)
135-
}
136-
137126
func GetEpayClient() *epay.Client {
138127
if operation_setting.PayAddress == "" || operation_setting.EpayId == "" || operation_setting.EpayKey == "" {
139128
return nil
@@ -248,13 +237,14 @@ func RequestEpay(c *gin.Context) {
248237
amount = dAmount.Div(dQuotaPerUnit).IntPart()
249238
}
250239
topUp := &model.TopUp{
251-
UserId: id,
252-
Amount: amount,
253-
Money: payMoney,
254-
TradeNo: tradeNo,
255-
PaymentMethod: req.PaymentMethod,
256-
CreateTime: time.Now().Unix(),
257-
Status: common.TopUpStatusPending,
240+
UserId: id,
241+
Amount: amount,
242+
Money: payMoney,
243+
TradeNo: tradeNo,
244+
PaymentMethod: req.PaymentMethod,
245+
PaymentProvider: model.PaymentProviderEpay,
246+
CreateTime: time.Now().Unix(),
247+
Status: common.TopUpStatusPending,
258248
}
259249
err = topUp.Insert()
260250
if err != nil {
@@ -379,15 +369,15 @@ func EpayNotify(c *gin.Context) {
379369
logger.LogWarn(c.Request.Context(), fmt.Sprintf("易支付 回调订单不存在 trade_no=%s callback_type=%s client_ip=%s verify_info=%q", verifyInfo.ServiceTradeNo, verifyInfo.Type, c.ClientIP(), common.GetJsonString(verifyInfo)))
380370
return
381371
}
382-
if isNonEpayPaymentMethodForEpayCallback(topUp.PaymentMethod) {
383-
logger.LogWarn(c.Request.Context(), fmt.Sprintf("易支付 订单支付方式不匹配 trade_no=%s order_payment_method=%s callback_type=%s client_ip=%s", verifyInfo.ServiceTradeNo, topUp.PaymentMethod, verifyInfo.Type, c.ClientIP()))
384-
return
385-
}
386-
if topUp.PaymentMethod != verifyInfo.Type {
387-
logger.LogWarn(c.Request.Context(), fmt.Sprintf("易支付 订单支付方式不匹配 trade_no=%s order_payment_method=%s callback_type=%s client_ip=%s", verifyInfo.ServiceTradeNo, topUp.PaymentMethod, verifyInfo.Type, c.ClientIP()))
372+
if topUp.PaymentProvider != model.PaymentProviderEpay {
373+
logger.LogWarn(c.Request.Context(), fmt.Sprintf("易支付 订单支付网关不匹配 trade_no=%s order_provider=%s callback_type=%s client_ip=%s", verifyInfo.ServiceTradeNo, topUp.PaymentProvider, verifyInfo.Type, c.ClientIP()))
388374
return
389375
}
390376
if topUp.Status == common.TopUpStatusPending {
377+
if topUp.PaymentMethod != verifyInfo.Type {
378+
logger.LogInfo(c.Request.Context(), fmt.Sprintf("易支付 实际支付方式与订单不同 trade_no=%s order_payment_method=%s actual_type=%s client_ip=%s", verifyInfo.ServiceTradeNo, topUp.PaymentMethod, verifyInfo.Type, c.ClientIP()))
379+
topUp.PaymentMethod = verifyInfo.Type
380+
}
391381
topUp.Status = common.TopUpStatusSuccess
392382
err := topUp.Update()
393383
if err != nil {

controller/topup_creem.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,14 @@ func (*CreemAdaptor) RequestPay(c *gin.Context, req *CreemPayRequest) {
106106

107107
// 先创建订单记录,使用产品配置的金额和充值额度
108108
topUp := &model.TopUp{
109-
UserId: id,
110-
Amount: selectedProduct.Quota, // 充值额度
111-
Money: selectedProduct.Price, // 支付金额
112-
TradeNo: referenceId,
113-
PaymentMethod: model.PaymentMethodCreem,
114-
CreateTime: time.Now().Unix(),
115-
Status: common.TopUpStatusPending,
109+
UserId: id,
110+
Amount: selectedProduct.Quota, // 充值额度
111+
Money: selectedProduct.Price, // 支付金额
112+
TradeNo: referenceId,
113+
PaymentMethod: model.PaymentMethodCreem,
114+
PaymentProvider: model.PaymentProviderCreem,
115+
CreateTime: time.Now().Unix(),
116+
Status: common.TopUpStatusPending,
116117
}
117118
err = topUp.Insert()
118119
if err != nil {
@@ -301,7 +302,7 @@ func handleCheckoutCompleted(c *gin.Context, event *CreemWebhookEvent) {
301302
// Try complete subscription order first
302303
LockOrder(referenceId)
303304
defer UnlockOrder(referenceId)
304-
if err := model.CompleteSubscriptionOrder(referenceId, common.GetJsonString(event), model.PaymentMethodCreem); err == nil {
305+
if err := model.CompleteSubscriptionOrder(referenceId, common.GetJsonString(event), model.PaymentProviderCreem, ""); err == nil {
305306
logger.LogInfo(c.Request.Context(), fmt.Sprintf("Creem 订阅订单处理成功 trade_no=%s creem_order_id=%s", referenceId, event.Object.Order.Id))
306307
c.Status(http.StatusOK)
307308
return

controller/topup_epay_guard_test.go

Lines changed: 0 additions & 31 deletions
This file was deleted.

controller/topup_stripe.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,14 @@ func (*StripeAdaptor) RequestPay(c *gin.Context, req *StripePayRequest) {
101101
}
102102

103103
topUp := &model.TopUp{
104-
UserId: id,
105-
Amount: req.Amount,
106-
Money: chargedMoney,
107-
TradeNo: referenceId,
108-
PaymentMethod: model.PaymentMethodStripe,
109-
CreateTime: time.Now().Unix(),
110-
Status: common.TopUpStatusPending,
104+
UserId: id,
105+
Amount: req.Amount,
106+
Money: chargedMoney,
107+
TradeNo: referenceId,
108+
PaymentMethod: model.PaymentMethodStripe,
109+
PaymentProvider: model.PaymentProviderStripe,
110+
CreateTime: time.Now().Unix(),
111+
Status: common.TopUpStatusPending,
111112
}
112113
err = topUp.Insert()
113114
if err != nil {
@@ -237,8 +238,8 @@ func sessionAsyncPaymentFailed(ctx context.Context, event stripe.Event, callerIp
237238
return
238239
}
239240

240-
if topUp.PaymentMethod != model.PaymentMethodStripe {
241-
logger.LogWarn(ctx, fmt.Sprintf("Stripe 异步支付失败但订单支付方式不匹配 trade_no=%s payment_method=%s client_ip=%s", referenceId, topUp.PaymentMethod, callerIp))
241+
if topUp.PaymentProvider != model.PaymentProviderStripe {
242+
logger.LogWarn(ctx, fmt.Sprintf("Stripe 异步支付失败但订单支付网关不匹配 trade_no=%s payment_provider=%s client_ip=%s", referenceId, topUp.PaymentProvider, callerIp))
242243
return
243244
}
244245

@@ -270,7 +271,7 @@ func fulfillOrder(ctx context.Context, event stripe.Event, referenceId string, c
270271
"currency": strings.ToUpper(event.GetObjectValue("currency")),
271272
"event_type": string(event.Type),
272273
}
273-
if err := model.CompleteSubscriptionOrder(referenceId, common.GetJsonString(payload), model.PaymentMethodStripe); err == nil {
274+
if err := model.CompleteSubscriptionOrder(referenceId, common.GetJsonString(payload), model.PaymentProviderStripe, ""); err == nil {
274275
logger.LogInfo(ctx, fmt.Sprintf("Stripe 订阅订单处理成功 trade_no=%s event_type=%s client_ip=%s", referenceId, string(event.Type), callerIp))
275276
return
276277
} else if err != nil && !errors.Is(err, model.ErrSubscriptionOrderNotFound) {
@@ -305,15 +306,15 @@ func sessionExpired(ctx context.Context, event stripe.Event) {
305306
// Subscription order expiration
306307
LockOrder(referenceId)
307308
defer UnlockOrder(referenceId)
308-
if err := model.ExpireSubscriptionOrder(referenceId, model.PaymentMethodStripe); err == nil {
309+
if err := model.ExpireSubscriptionOrder(referenceId, model.PaymentProviderStripe); err == nil {
309310
logger.LogInfo(ctx, fmt.Sprintf("Stripe 订阅订单已过期 trade_no=%s", referenceId))
310311
return
311312
} else if err != nil && !errors.Is(err, model.ErrSubscriptionOrderNotFound) {
312313
logger.LogError(ctx, fmt.Sprintf("Stripe 订阅订单过期处理失败 trade_no=%s error=%q", referenceId, err.Error()))
313314
return
314315
}
315316

316-
err := model.UpdatePendingTopUpStatus(referenceId, model.PaymentMethodStripe, common.TopUpStatusExpired)
317+
err := model.UpdatePendingTopUpStatus(referenceId, model.PaymentProviderStripe, common.TopUpStatusExpired)
317318
if errors.Is(err, model.ErrTopUpNotFound) {
318319
logger.LogWarn(ctx, fmt.Sprintf("Stripe 充值订单不存在,无法标记过期 trade_no=%s", referenceId))
319320
return

controller/topup_waffo.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,14 @@ func RequestWaffoPay(c *gin.Context) {
208208

209209
// 创建本地订单
210210
topUp := &model.TopUp{
211-
UserId: id,
212-
Amount: amount,
213-
Money: payMoney,
214-
TradeNo: merchantOrderId,
215-
PaymentMethod: model.PaymentMethodWaffo,
216-
CreateTime: time.Now().Unix(),
217-
Status: common.TopUpStatusPending,
211+
UserId: id,
212+
Amount: amount,
213+
Money: payMoney,
214+
TradeNo: merchantOrderId,
215+
PaymentMethod: model.PaymentMethodWaffo,
216+
PaymentProvider: model.PaymentProviderWaffo,
217+
CreateTime: time.Now().Unix(),
218+
Status: common.TopUpStatusPending,
218219
}
219220
if err := topUp.Insert(); err != nil {
220221
logger.LogError(c.Request.Context(), fmt.Sprintf("Waffo 创建充值订单失败 user_id=%d trade_no=%s amount=%d error=%q", id, merchantOrderId, req.Amount, err.Error()))
@@ -379,7 +380,7 @@ func handleWaffoPayment(c *gin.Context, wh *core.WebhookHandler, result *core.Pa
379380
logger.LogInfo(c.Request.Context(), fmt.Sprintf("Waffo 订单状态非成功,忽略充值 trade_no=%s order_status=%s client_ip=%s", result.MerchantOrderID, result.OrderStatus, c.ClientIP()))
380381
// 终态失败订单标记为 failed,避免永远停在 pending
381382
if result.MerchantOrderID != "" {
382-
if err := model.UpdatePendingTopUpStatus(result.MerchantOrderID, model.PaymentMethodWaffo, common.TopUpStatusFailed); err != nil &&
383+
if err := model.UpdatePendingTopUpStatus(result.MerchantOrderID, model.PaymentProviderWaffo, common.TopUpStatusFailed); err != nil &&
383384
!errors.Is(err, model.ErrTopUpNotFound) &&
384385
!errors.Is(err, model.ErrTopUpStatusInvalid) {
385386
logger.LogError(c.Request.Context(), fmt.Sprintf("Waffo 标记失败订单状态失败 trade_no=%s error=%q", result.MerchantOrderID, err.Error()))

controller/topup_waffo_pancake.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,14 @@ func RequestWaffoPancakePay(c *gin.Context) {
159159

160160
tradeNo := fmt.Sprintf("WAFFO_PANCAKE-%d-%d-%s", id, time.Now().UnixMilli(), randstr.String(6))
161161
topUp := &model.TopUp{
162-
UserId: id,
163-
Amount: normalizeWaffoPancakeTopUpAmount(req.Amount),
164-
Money: payMoney,
165-
TradeNo: tradeNo,
166-
PaymentMethod: model.PaymentMethodWaffoPancake,
167-
CreateTime: time.Now().Unix(),
168-
Status: common.TopUpStatusPending,
162+
UserId: id,
163+
Amount: normalizeWaffoPancakeTopUpAmount(req.Amount),
164+
Money: payMoney,
165+
TradeNo: tradeNo,
166+
PaymentMethod: model.PaymentMethodWaffoPancake,
167+
PaymentProvider: model.PaymentProviderWaffoPancake,
168+
CreateTime: time.Now().Unix(),
169+
Status: common.TopUpStatusPending,
169170
}
170171
if err := topUp.Insert(); err != nil {
171172
logger.LogError(c.Request.Context(), fmt.Sprintf("Waffo Pancake 创建充值订单失败 user_id=%d trade_no=%s amount=%d error=%q", id, tradeNo, req.Amount, err.Error()))

0 commit comments

Comments
 (0)