From 6df6012765a25c3eab8c02eaead6c2cc75984642 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:49:17 +0800 Subject: [PATCH 1/2] fix(payment): preserve disputed quota and add csrf guard --- frontend/lib/services/core/api-client.ts | 1 + internal/router/middlewares.go | 21 +++++++++++++++++++++ internal/router/router.go | 2 +- internal/service/payment.go | 4 ++-- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/frontend/lib/services/core/api-client.ts b/frontend/lib/services/core/api-client.ts index ff94a7c..8fa5ec2 100644 --- a/frontend/lib/services/core/api-client.ts +++ b/frontend/lib/services/core/api-client.ts @@ -23,6 +23,7 @@ const apiClient = axios.create({ withCredentials: apiConfig.withCredentials, headers: { 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', }, }); diff --git a/internal/router/middlewares.go b/internal/router/middlewares.go index 92c46eb..2c5e68f 100644 --- a/internal/router/middlewares.go +++ b/internal/router/middlewares.go @@ -17,6 +17,7 @@ limitations under the License. package router import ( + "net/http" "strconv" "time" @@ -24,10 +25,30 @@ import ( "github.com/linux-do/credit/internal/config" "github.com/linux-do/credit/internal/logger" "github.com/linux-do/credit/internal/otel_trace" + "github.com/linux-do/credit/internal/util" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) +func csrfMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + if method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete || method == http.MethodPatch { + path := c.Request.URL.Path + if path == "/pay/submit.php" || path == "/api.php" || path == "/pay/distribute" { + c.Next() + return + } + if c.GetHeader("X-Requested-With") != "XMLHttpRequest" { + c.AbortWithStatusJSON(http.StatusForbidden, util.Err("CSRF 验证失败")) + return + } + } + + c.Next() + } +} + func loggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 初始化 Trace diff --git a/internal/router/router.go b/internal/router/router.go index 4d60a9d..3e13c23 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -102,7 +102,7 @@ func Serve() { r.Use(sessions.Sessions(config.Config.App.SessionCookieName, sessionStore)) // 补充中间件 - r.Use(otelgin.Middleware(config.Config.App.AppName), loggerMiddleware()) + r.Use(otelgin.Middleware(config.Config.App.AppName), loggerMiddleware(), csrfMiddleware()) // 支付接口 r.Match([]string{"GET", "POST"}, "/pay/submit.php", payment.RequireSignatureAuth(), payment.CreateMerchantOrder) diff --git a/internal/service/payment.go b/internal/service/payment.go index f3f47c2..85d8ff0 100644 --- a/internal/service/payment.go +++ b/internal/service/payment.go @@ -140,9 +140,9 @@ func GetTodayUsedAmount(db *gorm.DB, userID uint64) (decimal.Decimal, error) { var total decimal.Decimal err := db.Model(&model.Order{}). - Where("payer_user_id = ? AND status = ? AND type IN ? AND trade_time >= ? AND trade_time < ?", + Where("payer_user_id = ? AND status IN ? AND type IN ? AND trade_time >= ? AND trade_time < ?", userID, - model.OrderStatusSuccess, + []model.OrderStatus{model.OrderStatusSuccess, model.OrderStatusDisputing, model.OrderStatusRefused}, []model.OrderType{model.OrderTypePayment, model.OrderTypeOnline, model.OrderTypeDistribute, model.OrderTypeTransfer}, todayStart, todayEnd). From a4fd0af1a448f746e81eb49f4b97bce41ae1c2cf Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:35:09 +0800 Subject: [PATCH 2/2] refactor(router): scope csrf middleware to api routes --- internal/router/middlewares.go | 5 ----- internal/router/router.go | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/router/middlewares.go b/internal/router/middlewares.go index 2c5e68f..b10b426 100644 --- a/internal/router/middlewares.go +++ b/internal/router/middlewares.go @@ -34,11 +34,6 @@ func csrfMiddleware() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method if method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete || method == http.MethodPatch { - path := c.Request.URL.Path - if path == "/pay/submit.php" || path == "/api.php" || path == "/pay/distribute" { - c.Next() - return - } if c.GetHeader("X-Requested-With") != "XMLHttpRequest" { c.AbortWithStatusJSON(http.StatusForbidden, util.Err("CSRF 验证失败")) return diff --git a/internal/router/router.go b/internal/router/router.go index 3e13c23..b4b1ab6 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -102,7 +102,7 @@ func Serve() { r.Use(sessions.Sessions(config.Config.App.SessionCookieName, sessionStore)) // 补充中间件 - r.Use(otelgin.Middleware(config.Config.App.AppName), loggerMiddleware(), csrfMiddleware()) + r.Use(otelgin.Middleware(config.Config.App.AppName), loggerMiddleware()) // 支付接口 r.Match([]string{"GET", "POST"}, "/pay/submit.php", payment.RequireSignatureAuth(), payment.CreateMerchantOrder) @@ -117,6 +117,7 @@ func Serve() { r.GET("/f/:id", upload.ServeFileByID) apiGroup := r.Group(config.Config.App.APIPrefix) + apiGroup.Use(csrfMiddleware()) { if !config.Config.App.IsProduction() { // Swagger