diff --git a/controller/subscription_payment_epay.go b/controller/subscription_payment_epay.go index a328436a0..72086dfe4 100644 --- a/controller/subscription_payment_epay.go +++ b/controller/subscription_payment_epay.go @@ -112,10 +112,17 @@ func SubscriptionRequestEpay(c *gin.Context) { } func SubscriptionEpayNotify(c *gin.Context) { - params := lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { - r[t] = c.Request.URL.Query().Get(t) + _ = c.Request.ParseForm() + params := lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string { + r[t] = c.Request.PostForm.Get(t) return r }, map[string]string{}) + if len(params) == 0 { + params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { + r[t] = c.Request.URL.Query().Get(t) + return r + }, map[string]string{}) + } client := GetEpayClient() if client == nil { @@ -147,10 +154,17 @@ func SubscriptionEpayNotify(c *gin.Context) { // SubscriptionEpayReturn handles browser return after payment. // It verifies the payload and completes the order, then redirects to console. func SubscriptionEpayReturn(c *gin.Context) { - params := lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { - r[t] = c.Request.URL.Query().Get(t) + _ = c.Request.ParseForm() + params := lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string { + r[t] = c.Request.PostForm.Get(t) return r }, map[string]string{}) + if len(params) == 0 { + params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { + r[t] = c.Request.URL.Query().Get(t) + return r + }, map[string]string{}) + } client := GetEpayClient() if client == nil { diff --git a/controller/topup.go b/controller/topup.go index 3e73fa156..62b6f9334 100644 --- a/controller/topup.go +++ b/controller/topup.go @@ -228,10 +228,17 @@ func UnlockOrder(tradeNo string) { } func EpayNotify(c *gin.Context) { - params := lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { - r[t] = c.Request.URL.Query().Get(t) + _ = c.Request.ParseForm() + params := lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string { + r[t] = c.Request.PostForm.Get(t) return r }, map[string]string{}) + if len(params) == 0 { + params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string { + r[t] = c.Request.URL.Query().Get(t) + return r + }, map[string]string{}) + } client := GetEpayClient() if client == nil { log.Println("易支付回调失败 未找到配置信息") diff --git a/router/api-router.go b/router/api-router.go index 50c817f49..80975715a 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -58,7 +58,7 @@ func SetApiRouter(router *gin.Engine) { userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), controller.PasskeyLoginFinish) //userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog) userRoute.GET("/logout", controller.Logout) - userRoute.GET("/epay/notify", controller.EpayNotify) + userRoute.POST("/epay/notify", controller.EpayNotify) userRoute.GET("/groups", controller.GetUserGroups) selfRoute := userRoute.Group("/") @@ -148,8 +148,9 @@ func SetApiRouter(router *gin.Engine) { } // Subscription payment callbacks (no auth) - apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify) + apiRouter.POST("/subscription/epay/notify", controller.SubscriptionEpayNotify) apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn) + apiRouter.POST("/subscription/epay/return", controller.SubscriptionEpayReturn) optionRoute := apiRouter.Group("/option") optionRoute.Use(middleware.RootAuth()) { diff --git a/service/billing.go b/service/billing.go index 17306c191..a93f9ca52 100644 --- a/service/billing.go +++ b/service/billing.go @@ -46,7 +46,11 @@ func PreConsumeBilling(c *gin.Context, preConsumedQuota int, relayInfo *relaycom if preConsumedQuota > 0 && !relayInfo.IsPlayground { _ = model.IncreaseTokenQuota(relayInfo.TokenId, relayInfo.TokenKey, preConsumedQuota) } - return types.NewErrorWithStatusCode(fmt.Errorf("订阅额度不足或未配置订阅: %s", err.Error()), types.ErrorCodeInsufficientUserQuota, http.StatusForbidden, types.ErrOptionWithSkipRetry(), types.ErrOptionWithNoRecordErrorLog()) + errMsg := err.Error() + if strings.Contains(errMsg, "no active subscription") || strings.Contains(errMsg, "subscription quota insufficient") { + return types.NewErrorWithStatusCode(fmt.Errorf("订阅额度不足或未配置订阅: %s", errMsg), types.ErrorCodeInsufficientUserQuota, http.StatusForbidden, types.ErrOptionWithSkipRetry(), types.ErrOptionWithNoRecordErrorLog()) + } + return types.NewErrorWithStatusCode(fmt.Errorf("订阅预扣失败: %s", errMsg), types.ErrorCodeQueryDataError, http.StatusInternalServerError) } relayInfo.BillingSource = BillingSourceSubscription @@ -91,7 +95,10 @@ func PreConsumeBilling(c *gin.Context, preConsumedQuota int, relayInfo *relaycom default: if err := trySubscription(); err != nil { // fallback only when subscription not available/insufficient - return tryWallet() + if err.GetErrorCode() == types.ErrorCodeInsufficientUserQuota { + return tryWallet() + } + return err } return nil } diff --git a/service/pre_consume_quota.go b/service/pre_consume_quota.go index 5d5b7bb20..3b049cb4f 100644 --- a/service/pre_consume_quota.go +++ b/service/pre_consume_quota.go @@ -31,9 +31,11 @@ func ReturnPreConsumedQuota(c *gin.Context, relayInfo *relaycommon.RelayInfo) { relayInfoCopy := *relayInfo if relayInfoCopy.BillingSource == BillingSourceSubscription { if needRefundSub { - refundWithRetry(func() error { + if err := refundWithRetry(func() error { return model.RefundSubscriptionPreConsume(relayInfoCopy.RequestId) - }) + }); err != nil { + common.SysLog("error refund subscription pre-consume: " + err.Error()) + } } // refund token quota only if needRefundToken && !relayInfoCopy.IsPlayground { @@ -52,19 +54,23 @@ func ReturnPreConsumedQuota(c *gin.Context, relayInfo *relaycommon.RelayInfo) { }) } -func refundWithRetry(fn func() error) { +func refundWithRetry(fn func() error) error { if fn == nil { - return + return nil } const maxAttempts = 3 + var lastErr error for i := 0; i < maxAttempts; i++ { if err := fn(); err == nil { - return + return nil + } else { + lastErr = err } if i < maxAttempts-1 { time.Sleep(time.Duration(200*(i+1)) * time.Millisecond) } } + return lastErr } // PreConsumeQuota checks if the user has enough quota to pre-consume.