diff --git a/backend/internal/e2e/rental_flow_test.go b/backend/internal/e2e/rental_flow_test.go index 619f376..62a509f 100644 --- a/backend/internal/e2e/rental_flow_test.go +++ b/backend/internal/e2e/rental_flow_test.go @@ -1,6 +1,7 @@ package e2e import ( + "context" "database/sql" "fmt" "os" @@ -45,7 +46,7 @@ func TestRentalFullFlowWithMockPayment(t *testing.T) { } orderDTO := mustCreateOrder(t, services.order, renter.ID, adjusted.ID) - paymentDTO, err := services.payment.Start(renter.ID, orderDTO.ID, payment.StartPaymentRequest{}, "127.0.0.1") + paymentDTO, err := services.payment.Start(t.Context(), renter.ID, orderDTO.ID, payment.StartPaymentRequest{}, "127.0.0.1") if err != nil { t.Fatalf("启动 mock 支付失败: %v", err) } @@ -115,7 +116,7 @@ func TestArbitrationReleaseDepositDoesNotRepublishListing(t *testing.T) { listingDTO := createListingUnderReview(t, services.listing, owner.ID) approved := adjustAndApproveListing(t, services.listing, adminID, listingDTO.ID) orderDTO := mustCreateOrder(t, services.order, renter.ID, approved.ID) - if _, err := services.payment.Start(renter.ID, orderDTO.ID, payment.StartPaymentRequest{}, "127.0.0.1"); err != nil { + if _, err := services.payment.Start(t.Context(), renter.ID, orderDTO.ID, payment.StartPaymentRequest{}, "127.0.0.1"); err != nil { t.Fatalf("启动 mock 支付失败: %v", err) } if _, err := services.order.SubmitHandoff(owner.ID, orderDTO.ID, order.SubmitHandoffRequest{Content: "账号:demo,密码:demo-pass"}); err != nil { @@ -171,7 +172,7 @@ func TestArbitrationReleaseDepositDoesNotRepublishListing(t *testing.T) { assertEqual(t, "仲裁退款状态", orderRow.RefundStatus, "refunded") assertEqual(t, "仲裁退款金额", orderRow.RefundAmountCent, int64(15000)) - walletAccount, err := services.wallet.Account(owner.ID) + walletAccount, err := services.wallet.Account(t.Context(), owner.ID) if err != nil { t.Fatalf("读取号主钱包失败: %v", err) } @@ -209,14 +210,14 @@ func newFlowServices(db *gorm.DB) flowServices { paymentRepo := payment.NewRepository(db, configRepo, orderRepo, walletRepo) disputeRepo := dispute.NewRepository(db) orderRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - refund, err := paymentRepo.StartRefund(orderID, refundAmountCent, bizType, remark) + refund, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } return refund.Status, nil }) disputeRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - refund, err := paymentRepo.StartRefund(orderID, refundAmountCent, bizType, remark) + refund, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } @@ -238,7 +239,7 @@ func newFlowServices(db *gorm.DB) flowServices { type fixedConfig map[string]string -func (c fixedConfig) FindValue(key string) (string, error) { +func (c fixedConfig) FindValue(ctx context.Context, key string) (string, error) { return c[key], nil } @@ -392,7 +393,7 @@ func createOwnerPaymentAccount(t *testing.T, service *paymentaccount.Service, ow func createListingUnderReview(t *testing.T, service *listing.Service, ownerID uint64) *listing.ListingDTO { t.Helper() - dto, err := service.Create(ownerID, listing.CreateRequest{ + dto, err := service.Create(t.Context(), ownerID, listing.CreateRequest{ Title: "E2E 烽火地带账号", Description: "用于完整链路测试", ServerRegion: "烽火地带", @@ -490,7 +491,7 @@ func assertFinanceDashboard(t *testing.T, service *adminfinance.Service, orderNo func assertWalletAndWithdrawal(t *testing.T, walletService *wallet.Service, withdrawalService *withdrawal.Service, ownerID uint64, adminID uint64, paymentAccountID uint64) { t.Helper() - accountBefore, err := walletService.Account(ownerID) + accountBefore, err := walletService.Account(t.Context(), ownerID) if err != nil { t.Fatalf("读取号主钱包失败: %v", err) } @@ -525,7 +526,7 @@ func assertWalletAndWithdrawal(t *testing.T, walletService *wallet.Service, with assertEqual(t, "提现完成状态", paid.Status, "completed") assertEqual(t, "提现到账金额", paid.ActualAmountCent, int64(10000)) - accountAfter, err := walletService.Account(ownerID) + accountAfter, err := walletService.Account(t.Context(), ownerID) if err != nil { t.Fatalf("读取提现后钱包失败: %v", err) } diff --git a/backend/internal/modules/adminauth/handler.go b/backend/internal/modules/adminauth/handler.go index 31f563e..c9ba282 100644 --- a/backend/internal/modules/adminauth/handler.go +++ b/backend/internal/modules/adminauth/handler.go @@ -20,7 +20,7 @@ func NewHandler(service *Service) *Handler { } func (h *Handler) Captcha(c *gin.Context) { - item, err := h.service.Captcha(c.Request.Context(), ) + item, err := h.service.Captcha(c.Request.Context()) if err != nil { writeAdminAuthError(c, err) return diff --git a/backend/internal/modules/adminauth/service.go b/backend/internal/modules/adminauth/service.go index cb11e47..d1e2870 100644 --- a/backend/internal/modules/adminauth/service.go +++ b/backend/internal/modules/adminauth/service.go @@ -39,11 +39,11 @@ func (s *Service) Refresh(ctx context.Context, refreshToken string) (*auth.Token return &pair, nil } -func (s *Service) Captcha(ctx context.Context, ) (*CaptchaDTO, error) { +func (s *Service) Captcha(ctx context.Context) (*CaptchaDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.Captcha(ctx, ) + return s.repo.Captcha(ctx) } func (s *Service) Login(ctx context.Context, username string, password string, captchaID string, captchaCode string) (LoginResult, error) { diff --git a/backend/internal/modules/listing/handler.go b/backend/internal/modules/listing/handler.go index 0609b28..5ea1a16 100644 --- a/backend/internal/modules/listing/handler.go +++ b/backend/internal/modules/listing/handler.go @@ -37,7 +37,7 @@ func (h *Handler) Create(c *gin.Context) { response.BadRequest(c, "发布信息不完整") return } - item, err := h.service.Create(ownerID, req) + item, err := h.service.Create(c.Request.Context(), ownerID, req) if err != nil { writeListingError(c, err) return @@ -57,7 +57,7 @@ func (h *Handler) ImportExternalUpload(c *gin.Context) { response.BadRequest(c, "上传 JSON 格式不正确") return } - result, err := h.service.ImportExternalUpload(req, ExternalUploadMeta{ + result, err := h.service.ImportExternalUpload(c.Request.Context(), req, ExternalUploadMeta{ IP: c.ClientIP(), UserAgent: c.GetHeader("User-Agent"), RawPayload: raw, @@ -90,7 +90,7 @@ func (h *Handler) Update(c *gin.Context) { response.BadRequest(c, "发布信息不完整") return } - item, err := h.service.Update(ownerID, id, req) + item, err := h.service.Update(c.Request.Context(), ownerID, id, req) if err != nil { writeListingError(c, err) return @@ -108,7 +108,7 @@ func (h *Handler) SubmitReview(c *gin.Context) { if !ok { return } - item, err := h.service.SubmitReview(ownerID, id) + item, err := h.service.SubmitReview(c.Request.Context(), ownerID, id) if err != nil { writeListingError(c, err) return diff --git a/backend/internal/modules/listing/service.go b/backend/internal/modules/listing/service.go index ce29c4a..45d34e1 100644 --- a/backend/internal/modules/listing/service.go +++ b/backend/internal/modules/listing/service.go @@ -1,6 +1,7 @@ package listing import ( + "context" "encoding/json" "errors" "fmt" @@ -36,7 +37,7 @@ type Service struct { } type ConfigReader interface { - FindValue(key string) (string, error) + FindValue(ctx context.Context, key string) (string, error) } const ( @@ -77,28 +78,28 @@ func NewService(repo *Repository, config ConfigReader) *Service { return &Service{repo: repo, config: config} } -func (s *Service) Create(ownerID uint64, req CreateRequest) (*ListingDTO, error) { +func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if !req.AgreedVirtualAssetSale || !req.AgreedSellerAgreement { return nil, ErrAgreementRequired } - rules, err := s.publishRules() + rules, err := s.publishRules(ctx) if err != nil { return nil, err } if err := validateRequest(req, rules); err != nil { return nil, err } - reviewRequired, err := s.reviewRequired() + reviewRequired, err := s.reviewRequired(ctx) if err != nil { return nil, err } return s.repo.Create(ownerID, req, reviewRequired) } -func (s *Service) ImportExternalUpload(req ExternalUploadRequest, meta ExternalUploadMeta) (*ExternalUploadResponse, error) { +func (s *Service) ImportExternalUpload(ctx context.Context, req ExternalUploadRequest, meta ExternalUploadMeta) (*ExternalUploadResponse, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } @@ -113,7 +114,7 @@ func (s *Service) ImportExternalUpload(req ExternalUploadRequest, meta ExternalU if len(items) > maxExternalUploadItems { return nil, ErrTooManyUploadItems } - rules, err := s.publishRules() + rules, err := s.publishRules(ctx) if err != nil { return nil, err } @@ -171,29 +172,29 @@ func (s *Service) ImportExternalUpload(req ExternalUploadRequest, meta ExternalU return resp, nil } -func (s *Service) Update(ownerID uint64, id uint64, req UpdateRequest) (*ListingDTO, error) { +func (s *Service) Update(ctx context.Context, ownerID uint64, id uint64, req UpdateRequest) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - rules, err := s.publishRules() + rules, err := s.publishRules(ctx) if err != nil { return nil, err } if err := validateRequest(req, rules); err != nil { return nil, err } - reviewRequired, err := s.reviewRequired() + reviewRequired, err := s.reviewRequired(ctx) if err != nil { return nil, err } return s.repo.Update(ownerID, id, req, reviewRequired) } -func (s *Service) SubmitReview(ownerID uint64, id uint64) (*ListingDTO, error) { +func (s *Service) SubmitReview(ctx context.Context, ownerID uint64, id uint64) (*ListingDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - reviewRequired, err := s.reviewRequired() + reviewRequired, err := s.reviewRequired(ctx) if err != nil { return nil, err } @@ -753,11 +754,11 @@ func hasScreenshotURL(urls []string) bool { return false } -func (s *Service) reviewRequired() (bool, error) { +func (s *Service) reviewRequired(ctx context.Context) (bool, error) { if s.config == nil { return false, nil } - value, err := s.config.FindValue(reviewRequiredConfigKey) + value, err := s.config.FindValue(ctx, reviewRequiredConfigKey) if err != nil { return false, err } @@ -768,12 +769,12 @@ func (s *Service) reviewRequired() (bool, error) { return required, nil } -func (s *Service) publishRules() (publishRules, error) { +func (s *Service) publishRules(ctx context.Context) (publishRules, error) { rules := publishRules{FireLevelMin: defaultFireLevelMin} if s.config == nil { return rules, nil } - value, err := s.config.FindValue(publishOptionsConfigKey) + value, err := s.config.FindValue(ctx, publishOptionsConfigKey) if err != nil { return rules, err } diff --git a/backend/internal/modules/listing/service_test.go b/backend/internal/modules/listing/service_test.go index 972e34f..4d2bb24 100644 --- a/backend/internal/modules/listing/service_test.go +++ b/backend/internal/modules/listing/service_test.go @@ -61,7 +61,7 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) { func TestCreateRequiresPublishAgreements(t *testing.T) { service := NewService(&Repository{}, nil) - _, err := service.Create(1, CreateRequest{}) + _, err := service.Create(t.Context(), 1, CreateRequest{}) if err != ErrAgreementRequired { t.Fatalf("expected ErrAgreementRequired, got %v", err) } diff --git a/backend/internal/modules/payment/handler.go b/backend/internal/modules/payment/handler.go index fe01409..741ccff 100644 --- a/backend/internal/modules/payment/handler.go +++ b/backend/internal/modules/payment/handler.go @@ -37,7 +37,7 @@ func (h *Handler) Start(c *gin.Context) { } var req StartPaymentRequest _ = c.ShouldBindJSON(&req) - item, err := h.service.Start(userID, orderID, req, c.ClientIP()) + item, err := h.service.Start(c.Request.Context(), userID, orderID, req, c.ClientIP()) if err != nil { writePaymentError(c, err) return @@ -55,7 +55,7 @@ func (h *Handler) Query(c *gin.Context) { if !ok { return } - item, err := h.service.Query(userID, orderID) + item, err := h.service.Query(c.Request.Context(), userID, orderID) if err != nil { writePaymentError(c, err) return @@ -74,7 +74,7 @@ func (h *Handler) WalletRecharge(c *gin.Context) { response.BadRequest(c, "充值金额不正确") return } - item, err := h.service.StartWalletRecharge(userID, req, c.ClientIP()) + item, err := h.service.StartWalletRecharge(c.Request.Context(), userID, req, c.ClientIP()) if err != nil { writePaymentError(c, err) return @@ -92,7 +92,7 @@ func (h *Handler) WalletRechargeQuery(c *gin.Context) { if !ok { return } - item, err := h.service.QueryWalletRecharge(userID, paymentID) + item, err := h.service.QueryWalletRecharge(c.Request.Context(), userID, paymentID) if err != nil { writePaymentError(c, err) return @@ -105,7 +105,7 @@ func (h *Handler) QueryRefundStatus(c *gin.Context) { if !ok { return } - item, err := h.service.QueryRefundStatus(orderID) + item, err := h.service.QueryRefundStatus(c.Request.Context(), orderID) if err != nil { writePaymentError(c, err) return @@ -118,7 +118,7 @@ func (h *Handler) AdminList(c *gin.Context) { if !ok { return } - result, err := h.service.AdminList(query) + result, err := h.service.AdminList(c.Request.Context(), query) if err != nil { writePaymentError(c, err) return @@ -148,7 +148,7 @@ func (h *Handler) LeshuaNotify(c *gin.Context) { contentType, rawPayload, ) - result, err := h.service.HandleLeshuaNotify(params, rawPayload, contentType) + result, err := h.service.HandleLeshuaNotify(c.Request.Context(), params, rawPayload, contentType) if err != nil || result == nil || !result.OK { log.Printf("[payment] leshua notify failed third_order_id=%s err=%v", params["third_order_id"], err) c.String(http.StatusOK, "FAIL") @@ -181,7 +181,7 @@ func (h *Handler) LakalaNotify(c *gin.Context) { contentType, rawPayload, ) - result, err := h.service.HandleNotify("lakala", params, rawPayload, contentType, authorization) + result, err := h.service.HandleNotify(c.Request.Context(), "lakala", params, rawPayload, contentType, authorization) if err != nil || result == nil || !result.OK { log.Printf("[payment] lakala notify failed third_order_id=%s err=%v", params["third_order_id"], err) c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "失败"}) diff --git a/backend/internal/modules/payment/repository.go b/backend/internal/modules/payment/repository.go index 484f1f0..8257d18 100644 --- a/backend/internal/modules/payment/repository.go +++ b/backend/internal/modules/payment/repository.go @@ -135,12 +135,12 @@ func runtimeConfigFromDTO(dto *paymentconfig.ConfigDTO) *runtimePaymentConfig { } } -func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) { +func (r *Repository) Start(ctx context.Context, userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) { defaultConfig, err := r.defaultRuntimeConfig() if err != nil { return nil, ErrPaymentUnavailable } - payment, orderRow, err := r.preparePayment(userID, orderID, req, *defaultConfig) + payment, orderRow, err := r.preparePayment(ctx, userID, orderID, req, *defaultConfig) if err != nil { return nil, err } @@ -154,7 +154,7 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques return &dto, nil } if runtimeConfig.isMockMode() { - if err := r.confirmPaid(payment, "2", time.Now(), map[string]string{ + if err := r.confirmPaid(ctx, payment, "2", time.Now(), map[string]string{ "mock": "true", "third_order_id": payment.ThirdOrderID, "leshua_order_id": payment.ProviderOrderID, @@ -162,7 +162,7 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques }, channelSourceMock); err != nil { return nil, err } - latest, err := r.findPaymentByID(payment.ID) + latest, err := r.findPaymentByID(ctx, payment.ID) if err != nil { return nil, err } @@ -176,13 +176,13 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques return &dto, nil } if runtimeConfig.Channel == nil { - _ = r.markPaymentFailed(payment.ID, nil, "payment channel unavailable") + _ = r.markPaymentFailed(ctx, payment.ID, nil, "payment channel unavailable") return nil, ErrPaymentUnavailable } log.Printf("[payment] payment start order_id=%d order_no=%s payment_id=%d provider=%s amount_cent=%d third_order_id=%s", orderID, orderRow.OrderNo, payment.ID, runtimeConfig.Provider, payment.AmountCent, payment.ThirdOrderID) - resp, err := runtimeConfig.Channel.CreatePayment(context.Background(), channelCreatePaymentRequest{ + resp, err := runtimeConfig.Channel.CreatePayment(ctx, channelCreatePaymentRequest{ ThirdOrderID: payment.ThirdOrderID, AmountCent: payment.AmountCent, PayWay: payment.PayWay, @@ -194,18 +194,18 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques Attach: orderRow.OrderNo, }) if err != nil { - _ = r.markPaymentFailed(payment.ID, nil, err.Error()) + _ = r.markPaymentFailed(ctx, payment.ID, nil, err.Error()) log.Printf("[payment] payment request failed order_id=%d payment_id=%d provider=%s amount_cent=%d err=%v", orderID, payment.ID, runtimeConfig.Provider, payment.AmountCent, err) return nil, err } if !resp.OK { - _ = r.markPaymentFailed(payment.ID, resp.Raw, resp.ErrorMessage) + _ = r.markPaymentFailed(ctx, payment.ID, resp.Raw, resp.ErrorMessage) log.Printf("[payment] payment rejected order_id=%d payment_id=%d provider=%s amount_cent=%d code=%s message=%s", orderID, payment.ID, runtimeConfig.Provider, payment.AmountCent, firstNonEmpty(resp.Raw["code"], resp.Raw["resp_code"], resp.Raw["result_code"]), resp.ErrorMessage) return nil, ErrPaymentUnavailable } - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "paying", "provider_order_id": resp.ProviderOrderID, "pay_way": firstNonEmpty(resp.PayWay, payment.PayWay), @@ -217,7 +217,7 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques }).Error; err != nil { return nil, err } - latest, err := r.findPaymentByID(payment.ID) + latest, err := r.findPaymentByID(ctx, payment.ID) if err != nil { return nil, err } @@ -228,7 +228,7 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques return &dto, nil } -func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymentRequest, clientIP string) (*PaymentDTO, error) { +func (r *Repository) StartWalletRecharge(ctx context.Context, userID uint64, req WalletRechargePaymentRequest, clientIP string) (*PaymentDTO, error) { amountCent := req.AmountCent if userID == 0 || amountCent < moneyCent(MinWalletRechargeAmount) { return nil, ErrPaymentCannotStart @@ -237,12 +237,12 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen if err != nil { return nil, ErrPaymentUnavailable } - payment, err := r.createWalletRechargePayment(userID, amountCent, req, *runtimeConfig) + payment, err := r.createWalletRechargePayment(ctx, userID, amountCent, req, *runtimeConfig) if err != nil { return nil, err } if runtimeConfig.isMockMode() { - if err := r.confirmPaid(payment, "2", time.Now(), map[string]string{ + if err := r.confirmPaid(ctx, payment, "2", time.Now(), map[string]string{ "mock": "true", "third_order_id": payment.ThirdOrderID, "leshua_order_id": payment.ProviderOrderID, @@ -250,7 +250,7 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen }, channelSourceMock); err != nil { return nil, err } - latest, err := r.findPaymentByID(payment.ID) + latest, err := r.findPaymentByID(ctx, payment.ID) if err != nil { return nil, err } @@ -259,12 +259,12 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen return &dto, nil } if runtimeConfig.Channel == nil { - _ = r.markPaymentFailed(payment.ID, nil, "payment channel unavailable") + _ = r.markPaymentFailed(ctx, payment.ID, nil, "payment channel unavailable") return nil, ErrPaymentUnavailable } log.Printf("[payment] wallet recharge start user_id=%d payment_id=%d provider=%s amount_cent=%d third_order_id=%s", userID, payment.ID, runtimeConfig.Provider, payment.AmountCent, payment.ThirdOrderID) - resp, err := runtimeConfig.Channel.CreatePayment(context.Background(), channelCreatePaymentRequest{ + resp, err := runtimeConfig.Channel.CreatePayment(ctx, channelCreatePaymentRequest{ ThirdOrderID: payment.ThirdOrderID, AmountCent: payment.AmountCent, PayWay: payment.PayWay, @@ -276,18 +276,18 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen Attach: payment.PaymentNo, }) if err != nil { - _ = r.markPaymentFailed(payment.ID, nil, err.Error()) + _ = r.markPaymentFailed(ctx, payment.ID, nil, err.Error()) log.Printf("[payment] wallet recharge request failed user_id=%d payment_id=%d provider=%s amount_cent=%d err=%v", userID, payment.ID, runtimeConfig.Provider, payment.AmountCent, err) return nil, err } if !resp.OK { - _ = r.markPaymentFailed(payment.ID, resp.Raw, resp.ErrorMessage) + _ = r.markPaymentFailed(ctx, payment.ID, resp.Raw, resp.ErrorMessage) log.Printf("[payment] wallet recharge rejected user_id=%d payment_id=%d provider=%s amount_cent=%d code=%s message=%s", userID, payment.ID, runtimeConfig.Provider, payment.AmountCent, firstNonEmpty(resp.Raw["code"], resp.Raw["resp_code"], resp.Raw["result_code"]), resp.ErrorMessage) return nil, ErrPaymentUnavailable } - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "paying", "provider_order_id": resp.ProviderOrderID, "pay_way": firstNonEmpty(resp.PayWay, payment.PayWay), @@ -299,7 +299,7 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen }).Error; err != nil { return nil, err } - latest, err := r.findPaymentByID(payment.ID) + latest, err := r.findPaymentByID(ctx, payment.ID) if err != nil { return nil, err } @@ -310,9 +310,9 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen return &dto, nil } -func (r *Repository) QueryWalletRecharge(userID uint64, paymentID uint64) (*PaymentDTO, error) { +func (r *Repository) QueryWalletRecharge(ctx context.Context, userID uint64, paymentID uint64) (*PaymentDTO, error) { var payment model.PaymentOrder - if err := r.db.Where("id = ? AND user_id = ? AND order_id = 0", paymentID, userID).First(&payment).Error; err != nil { + if err := r.db.WithContext(ctx).Where("id = ? AND user_id = ? AND order_id = 0", paymentID, userID).First(&payment).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrPaymentNotFound } @@ -329,14 +329,14 @@ func (r *Repository) QueryWalletRecharge(userID uint64, paymentID uint64) (*Paym if runtimeConfig.Channel == nil { return nil, ErrPaymentUnavailable } - resp, err := runtimeConfig.Channel.QueryPayment(context.Background(), payment.ThirdOrderID, payment.ProviderOrderID) + resp, err := runtimeConfig.Channel.QueryPayment(ctx, payment.ThirdOrderID, payment.ProviderOrderID) if err != nil { return nil, err } - if err := r.applyChannelStatus(&payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil { + if err := r.applyChannelStatus(ctx, &payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil { return nil, err } - latest, err := r.findPaymentByID(payment.ID) + latest, err := r.findPaymentByID(ctx, payment.ID) if err != nil { return nil, err } @@ -344,9 +344,9 @@ func (r *Repository) QueryWalletRecharge(userID uint64, paymentID uint64) (*Paym return &dto, nil } -func (r *Repository) Query(userID uint64, orderID uint64) (*PaymentDTO, error) { +func (r *Repository) Query(ctx context.Context, userID uint64, orderID uint64) (*PaymentDTO, error) { var payment model.PaymentOrder - if err := r.db.Where("order_id = ? AND user_id = ? AND biz_type = ?", orderID, userID, "order_pay").Order("id DESC").First(&payment).Error; err != nil { + if err := r.db.WithContext(ctx).Where("order_id = ? AND user_id = ? AND biz_type = ?", orderID, userID, "order_pay").Order("id DESC").First(&payment).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrPaymentNotFound } @@ -363,14 +363,14 @@ func (r *Repository) Query(userID uint64, orderID uint64) (*PaymentDTO, error) { if runtimeConfig.Channel == nil { return nil, ErrPaymentUnavailable } - resp, err := runtimeConfig.Channel.QueryPayment(context.Background(), payment.ThirdOrderID, payment.ProviderOrderID) + resp, err := runtimeConfig.Channel.QueryPayment(ctx, payment.ThirdOrderID, payment.ProviderOrderID) if err != nil { return nil, err } - if err := r.applyChannelStatus(&payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil { + if err := r.applyChannelStatus(ctx, &payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil { return nil, err } - latest, err := r.findPaymentByID(payment.ID) + latest, err := r.findPaymentByID(ctx, payment.ID) if err != nil { return nil, err } @@ -378,17 +378,17 @@ func (r *Repository) Query(userID uint64, orderID uint64) (*PaymentDTO, error) { return &dto, nil } -func (r *Repository) HandleLeshuaNotify(params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) { - return r.HandleNotify("leshua", params, rawPayload, contentType, "") +func (r *Repository) HandleLeshuaNotify(ctx context.Context, params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) { + return r.HandleNotify(ctx, "leshua", params, rawPayload, contentType, "") } -func (r *Repository) HandleNotify(provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) { +func (r *Repository) HandleNotify(ctx context.Context, provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) { // 退款通知会携带 merchant_refund_id 或 leshua_refund_id。 if params["merchant_refund_id"] != "" || params["leshua_refund_id"] != "" || params["provider_refund_id"] != "" { - return r.HandleRefundNotify(provider, params, rawPayload, contentType, authorization) + return r.HandleRefundNotify(ctx, provider, params, rawPayload, contentType, authorization) } - payment, err := r.findPaymentForNotify(params) + payment, err := r.findPaymentForNotify(ctx, params) if err != nil { return nil, err } @@ -396,28 +396,28 @@ func (r *Repository) HandleNotify(provider string, params map[string]string, raw if err != nil { return nil, ErrPaymentUnavailable } - verify, err := r.verifyNotify(payment, runtimeConfig, params, rawPayload, contentType, authorization) + verify, err := r.verifyNotify(ctx, payment, runtimeConfig, params, rawPayload, contentType, authorization) if err != nil { return nil, err } if amount := parseCent(params["amount"]); amount > 0 && amount != payment.AmountCent { - if err := r.recordNotifyDiagnostic(payment.ID, params, rawPayload, contentType, verify, "amount_mismatch"); err != nil { + if err := r.recordNotifyDiagnostic(ctx, payment.ID, params, rawPayload, contentType, verify, "amount_mismatch"); err != nil { log.Printf("[payment] %s notify diagnostic save failed third_order_id=%s err=%v", runtimeConfig.Provider, params["third_order_id"], err) } return nil, ErrPaymentVerifyFailed } raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, "verified") - if err := r.applyChannelStatus(payment, normalizeNotifyPaymentStatus(runtimeConfig.Provider, params["status"]), params["pay_time"], raw, channelSourceNotify); err != nil { + if err := r.applyChannelStatus(ctx, payment, normalizeNotifyPaymentStatus(runtimeConfig.Provider, params["status"]), params["pay_time"], raw, channelSourceNotify); err != nil { return nil, err } return &NotifyResult{OK: true, Message: "000000"}, nil } // StartRefund 创建退款单,并在本地落库后调用乐刷退款接口。 -func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType string, remark string) (*RefundDTO, error) { +func (r *Repository) StartRefund(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (*RefundDTO, error) { var originalPayment model.PaymentOrder - if err := r.db.Where("order_id = ? AND status = 'paid' AND biz_type = 'order_pay'", orderID).Order("id DESC").First(&originalPayment).Error; err != nil { + if err := r.db.WithContext(ctx).Where("order_id = ? AND status = 'paid' AND biz_type = 'order_pay'", orderID).Order("id DESC").First(&originalPayment).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrPaymentNotFound } @@ -429,7 +429,7 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType } var existingRefund model.PaymentOrder - err = r.db.Where("order_id = ? AND biz_type = ? AND status NOT IN ('failed')", orderID, bizType).Order("id DESC").First(&existingRefund).Error + err = r.db.WithContext(ctx).Where("order_id = ? AND biz_type = ? AND status NOT IN ('failed')", orderID, bizType).Order("id DESC").First(&existingRefund).Error if err == nil { dto := toRefundDTO(existingRefund) return &dto, nil @@ -468,32 +468,32 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType if remark != "" { refundOrder.RawResponse = datatypes.JSON([]byte(fmt.Sprintf(`{"mock":"true","remark":"%s"}`, remark))) } - if err := r.db.Create(&refundOrder).Error; err != nil { + if err := r.db.WithContext(ctx).Create(&refundOrder).Error; err != nil { return nil, err } r.recordConfigUsage(runtimeConfig, &refundOrder) - if err := r.updateOrderRefundStatus(orderID, refundAmountCent); err != nil { + if err := r.updateOrderRefundStatus(ctx, orderID, refundAmountCent); err != nil { log.Printf("[payment] mock update order refund status failed order_id=%d err=%v", orderID, err) } dto := toRefundDTO(refundOrder) return &dto, nil } - if err := r.db.Create(&refundOrder).Error; err != nil { + if err := r.db.WithContext(ctx).Create(&refundOrder).Error; err != nil { return nil, err } log.Printf("[payment] refund start order_id=%d order_no=%s payment_id=%d biz_type=%s provider=%s amount_cent=%d merchant_refund_id=%s origin_third_order_id=%s origin_provider_order_id=%s", orderID, originalPayment.OrderNo, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, merchantRefundID, originalPayment.ThirdOrderID, refundOriginProviderOrderID(originalPayment)) r.recordConfigUsage(runtimeConfig, &refundOrder) - if err := r.markOrderRefunding(orderID, refundAmountCent); err != nil { + if err := r.markOrderRefunding(ctx, orderID, refundAmountCent); err != nil { log.Printf("[payment] mark order refunding failed order_id=%d err=%v", orderID, err) } if runtimeConfig.Channel == nil { - _ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": "payment channel unavailable"}) + _ = r.markRefundFailed(ctx, refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": "payment channel unavailable"}) return nil, ErrPaymentUnavailable } - resp, err := runtimeConfig.Channel.CreateRefund(context.Background(), channelCreateRefundRequest{ + resp, err := runtimeConfig.Channel.CreateRefund(ctx, channelCreateRefundRequest{ ThirdOrderID: originalPayment.ThirdOrderID, ProviderOrderID: refundOriginProviderOrderID(originalPayment), MerchantRefundID: merchantRefundID, @@ -503,13 +503,13 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType Remark: remark, }) if err != nil { - _ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": err.Error()}) + _ = r.markRefundFailed(ctx, refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": err.Error()}) log.Printf("[payment] refund request failed order_id=%d payment_id=%d biz_type=%s provider=%s amount_cent=%d err=%v", orderID, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, err) return nil, err } if !resp.OK { - _ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, resp.Raw) + _ = r.markRefundFailed(ctx, refundOrder.ID, orderID, refundAmountCent, resp.Raw) log.Printf("[payment] refund rejected order_id=%d payment_id=%d biz_type=%s provider=%s amount_cent=%d code=%s message=%s", orderID, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, firstNonEmpty(resp.Raw["code"], resp.Raw["resp_code"], resp.Raw["result_code"]), resp.ErrorMessage) return nil, ErrPaymentUnavailable @@ -524,7 +524,7 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType } else if resp.Status == "failed" { refundStatus = "failed" } - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", refundOrder.ID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", refundOrder.ID).Updates(map[string]any{ "status": refundStatus, "provider_order_id": resp.ProviderRefundID, "raw_request": jsonMap(resp.RawRequest), @@ -535,12 +535,12 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType } if refundStatus == "refunded" { - _ = r.updateOrderRefundStatus(orderID, refundAmountCent) + _ = r.updateOrderRefundStatus(ctx, orderID, refundAmountCent) refundOrder.PaidAt = paidAt } else if refundStatus == "failed" { - _ = r.markOrderRefundFailed(orderID, refundAmountCent) + _ = r.markOrderRefundFailed(ctx, orderID, refundAmountCent) } else { - _ = r.markOrderRefunding(orderID, refundAmountCent) + _ = r.markOrderRefunding(ctx, orderID, refundAmountCent) } refundOrder.Status = refundStatus refundOrder.ProviderOrderID = resp.ProviderRefundID @@ -552,9 +552,9 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType } // QueryRefundStatus 查询订单最近一笔退款状态。 -func (r *Repository) QueryRefundStatus(orderID uint64) (*RefundDTO, error) { +func (r *Repository) QueryRefundStatus(ctx context.Context, orderID uint64) (*RefundDTO, error) { var payment model.PaymentOrder - if err := r.db.Where("order_id = ? AND biz_type IN ?", orderID, refundBizTypes).Order("id DESC").First(&payment).Error; err != nil { + if err := r.db.WithContext(ctx).Where("order_id = ? AND biz_type IN ?", orderID, refundBizTypes).Order("id DESC").First(&payment).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrPaymentNotFound } @@ -571,7 +571,7 @@ func (r *Repository) QueryRefundStatus(orderID uint64) (*RefundDTO, error) { if runtimeConfig.Channel == nil { return nil, ErrPaymentUnavailable } - resp, err := runtimeConfig.Channel.QueryRefund(context.Background(), channelQueryRefundRequest{ + resp, err := runtimeConfig.Channel.QueryRefund(ctx, channelQueryRefundRequest{ ThirdOrderID: payment.ThirdOrderID, MerchantRefundID: payment.ThirdOrderID, ProviderRefundID: payment.ProviderOrderID, @@ -581,7 +581,7 @@ func (r *Repository) QueryRefundStatus(orderID uint64) (*RefundDTO, error) { } if resp.Status == "refunded" { now := time.Now() - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "refunded", "paid_at": now, "raw_response": jsonMap(withRawSource(resp.Raw, channelSourceQuery)), @@ -590,26 +590,26 @@ func (r *Repository) QueryRefundStatus(orderID uint64) (*RefundDTO, error) { } payment.Status = "refunded" payment.PaidAt = &now - _ = r.updateOrderRefundStatus(orderID, payment.AmountCent) + _ = r.updateOrderRefundStatus(ctx, orderID, payment.AmountCent) } else if resp.Status == "failed" { - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "failed", "raw_response": jsonMap(withRawSource(resp.Raw, channelSourceQuery)), }).Error; err != nil { return nil, err } payment.Status = "failed" - _ = r.markOrderRefundFailed(orderID, payment.AmountCent) + _ = r.markOrderRefundFailed(ctx, orderID, payment.AmountCent) } dto := toRefundDTO(payment) return &dto, nil } -func (r *Repository) AdminList(query AdminPaymentQuery) (*PaginatedResult, error) { - db := r.db.Table("payment_orders AS p"). +func (r *Repository) AdminList(ctx context.Context, query AdminPaymentQuery) (*PaginatedResult, error) { + db := r.db.WithContext(ctx).Table("payment_orders AS p"). Select("p.*, COALESCE(u.phone, '') AS user_phone"). Joins("LEFT JOIN users AS u ON u.id = p.user_id") - countDB := r.db.Model(&model.PaymentOrder{}) + countDB := r.db.WithContext(ctx).Model(&model.PaymentOrder{}) if query.UserID > 0 { db = db.Where("p.user_id = ?", query.UserID) countDB = countDB.Where("user_id = ?", query.UserID) @@ -651,8 +651,8 @@ func (r *Repository) AdminList(query AdminPaymentQuery) (*PaginatedResult, error } // HandleRefundNotify 处理渠道退款通知。 -func (r *Repository) HandleRefundNotify(provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) { - payment, err := r.findRefundPaymentForNotify(params) +func (r *Repository) HandleRefundNotify(ctx context.Context, provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) { + payment, err := r.findRefundPaymentForNotify(ctx, params) if err != nil { return nil, err } @@ -660,7 +660,7 @@ func (r *Repository) HandleRefundNotify(provider string, params map[string]strin if err != nil { return nil, ErrPaymentUnavailable } - verify, err := r.verifyNotify(payment, runtimeConfig, params, rawPayload, contentType, authorization) + verify, err := r.verifyNotify(ctx, payment, runtimeConfig, params, rawPayload, contentType, authorization) if err != nil { return nil, err } @@ -670,7 +670,7 @@ func (r *Repository) HandleRefundNotify(provider string, params map[string]strin switch status { case "refunded": now := time.Now() - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "refunded", "paid_at": now, "notified_at": now, @@ -678,16 +678,16 @@ func (r *Repository) HandleRefundNotify(provider string, params map[string]strin }).Error; err != nil { return nil, err } - _ = r.updateOrderRefundStatus(payment.OrderID, payment.AmountCent) + _ = r.updateOrderRefundStatus(ctx, payment.OrderID, payment.AmountCent) case "failed": - r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "failed", "notified_at": time.Now(), "raw_response": jsonMap(raw), }) - _ = r.markOrderRefundFailed(payment.OrderID, payment.AmountCent) + _ = r.markOrderRefundFailed(ctx, payment.OrderID, payment.AmountCent) default: - r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ + r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{ "status": "refunding", "raw_response": jsonMap(raw), }) @@ -696,42 +696,42 @@ func (r *Repository) HandleRefundNotify(provider string, params map[string]strin } // updateOrderRefundStatus 更新订单退款成功状态。 -func (r *Repository) updateOrderRefundStatus(orderID uint64, refundAmountCent int64) error { +func (r *Repository) updateOrderRefundStatus(ctx context.Context, orderID uint64, refundAmountCent int64) error { now := time.Now() - return r.db.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{ + return r.db.WithContext(ctx).Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{ "refund_status": "refunded", "refund_amount_cent": refundAmountCent, "refunded_at": now, }).Error } -func (r *Repository) markOrderRefunding(orderID uint64, refundAmountCent int64) error { - return r.db.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{ +func (r *Repository) markOrderRefunding(ctx context.Context, orderID uint64, refundAmountCent int64) error { + return r.db.WithContext(ctx).Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{ "refund_status": "refunding", "refund_amount_cent": refundAmountCent, "refunded_at": nil, }).Error } -func (r *Repository) markOrderRefundFailed(orderID uint64, refundAmountCent int64) error { - return r.db.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{ +func (r *Repository) markOrderRefundFailed(ctx context.Context, orderID uint64, refundAmountCent int64) error { + return r.db.WithContext(ctx).Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{ "refund_status": "failed", "refund_amount_cent": refundAmountCent, "refunded_at": nil, }).Error } -func (r *Repository) markRefundFailed(paymentID uint64, orderID uint64, refundAmountCent int64, raw map[string]string) error { +func (r *Repository) markRefundFailed(ctx context.Context, paymentID uint64, orderID uint64, refundAmountCent int64, raw map[string]string) error { if raw == nil { raw = map[string]string{"error": "refund failed"} } - if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(map[string]any{ + if err := r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(map[string]any{ "status": "failed", "raw_response": jsonMap(raw), }).Error; err != nil { return err } - return r.markOrderRefundFailed(orderID, refundAmountCent) + return r.markOrderRefundFailed(ctx, orderID, refundAmountCent) } // toRefundDTO 将支付表里的退款单转换为接口 DTO。 @@ -751,10 +751,10 @@ func toRefundDTO(payment model.PaymentOrder) RefundDTO { } } -func (r *Repository) preparePayment(userID uint64, orderID uint64, req StartPaymentRequest, runtimeConfig runtimePaymentConfig) (*model.PaymentOrder, *model.RentalOrder, error) { +func (r *Repository) preparePayment(ctx context.Context, userID uint64, orderID uint64, req StartPaymentRequest, runtimeConfig runtimePaymentConfig) (*model.PaymentOrder, *model.RentalOrder, error) { var paymentID uint64 var orderRow model.RentalOrder - err := r.db.Transaction(func(tx *gorm.DB) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var row model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("id = ? AND renter_id = ?", orderID, userID). @@ -807,7 +807,7 @@ func (r *Repository) preparePayment(userID uint64, orderID uint64, req StartPaym if err != nil { return nil, nil, err } - payment, err := r.findPaymentByID(paymentID) + payment, err := r.findPaymentByID(ctx, paymentID) if err != nil { return nil, nil, err } @@ -875,7 +875,7 @@ func newOrderPayment(row model.RentalOrder, amountCent int64, req StartPaymentRe return payment, nil } -func (r *Repository) createWalletRechargePayment(userID uint64, amountCent int64, req WalletRechargePaymentRequest, runtimeConfig runtimePaymentConfig) (*model.PaymentOrder, error) { +func (r *Repository) createWalletRechargePayment(ctx context.Context, userID uint64, amountCent int64, req WalletRechargePaymentRequest, runtimeConfig runtimePaymentConfig) (*model.PaymentOrder, error) { paymentNo, err := newPaymentNo() if err != nil { return nil, err @@ -899,13 +899,13 @@ func (r *Repository) createWalletRechargePayment(userID uint64, amountCent int64 payment.ProviderOrderID = "MOCK" + paymentNo payment.TDCode = "mock://payment/recharge/" + paymentNo } - if err := r.db.Create(&payment).Error; err != nil { + if err := r.db.WithContext(ctx).Create(&payment).Error; err != nil { return nil, err } return &payment, nil } -func (r *Repository) applyChannelStatus(payment *model.PaymentOrder, status string, payTime string, raw map[string]string, source string) error { +func (r *Repository) applyChannelStatus(ctx context.Context, payment *model.PaymentOrder, status string, payTime string, raw map[string]string, source string) error { switch status { case "paid": paidAt := parseChannelTime(payTime) @@ -913,17 +913,17 @@ func (r *Repository) applyChannelStatus(payment *model.PaymentOrder, status stri now := time.Now() paidAt = &now } - return r.confirmPaid(payment, status, *paidAt, raw, source) + return r.confirmPaid(ctx, payment, status, *paidAt, raw, source) case "closed": - return r.updateChannelStatus(payment.ID, "closed", raw, source) + return r.updateChannelStatus(ctx, payment.ID, "closed", raw, source) case "failed": - return r.updateChannelStatus(payment.ID, "failed", raw, source) + return r.updateChannelStatus(ctx, payment.ID, "failed", raw, source) default: - return r.updateChannelStatus(payment.ID, "paying", raw, source) + return r.updateChannelStatus(ctx, payment.ID, "paying", raw, source) } } -func (r *Repository) updateChannelStatus(paymentID uint64, status string, raw map[string]string, source string) error { +func (r *Repository) updateChannelStatus(ctx context.Context, paymentID uint64, status string, raw map[string]string, source string) error { updates := map[string]any{ "status": status, "raw_response": jsonMap(withRawSource(raw, source)), @@ -931,16 +931,16 @@ func (r *Repository) updateChannelStatus(paymentID uint64, status string, raw ma if source == channelSourceNotify { updates["notified_at"] = time.Now() } - return r.db.Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(updates).Error + return r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(updates).Error } -func (r *Repository) confirmPaid(payment *model.PaymentOrder, status string, paidAt time.Time, raw map[string]string, source string) error { +func (r *Repository) confirmPaid(ctx context.Context, payment *model.PaymentOrder, status string, paidAt time.Time, raw map[string]string, source string) error { if payment.Status != "paid" { if payment.OrderID == 0 { if r.walletRepo == nil { return ErrDependencyUnavailable } - if err := r.walletRepo.ConfirmRechargeFromChannel(context.Background(), payment.UserID, firstNonEmpty(payment.ProviderOrderID, payment.PaymentNo), payment.AmountCent); err != nil { + if err := r.walletRepo.ConfirmRechargeFromChannel(ctx, payment.UserID, firstNonEmpty(payment.ProviderOrderID, payment.PaymentNo), payment.AmountCent); err != nil { return err } } else { @@ -961,34 +961,34 @@ func (r *Repository) confirmPaid(payment *model.PaymentOrder, status string, pai if source == channelSourceNotify { updates["notified_at"] = time.Now() } - return r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(updates).Error + return r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(updates).Error } -func (r *Repository) markPaymentFailed(paymentID uint64, raw map[string]string, message string) error { +func (r *Repository) markPaymentFailed(ctx context.Context, paymentID uint64, raw map[string]string, message string) error { if raw == nil { raw = map[string]string{"error": message} } - return r.db.Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(map[string]any{ + return r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(map[string]any{ "status": "failed", "raw_response": jsonMap(raw), }).Error } -func (r *Repository) findPaymentByID(paymentID uint64) (*model.PaymentOrder, error) { +func (r *Repository) findPaymentByID(ctx context.Context, paymentID uint64) (*model.PaymentOrder, error) { var payment model.PaymentOrder - if err := r.db.First(&payment, paymentID).Error; err != nil { + if err := r.db.WithContext(ctx).First(&payment, paymentID).Error; err != nil { return nil, err } return &payment, nil } -func (r *Repository) findPaymentForNotify(params map[string]string) (*model.PaymentOrder, error) { +func (r *Repository) findPaymentForNotify(ctx context.Context, params map[string]string) (*model.PaymentOrder, error) { thirdOrderID := params["third_order_id"] if thirdOrderID == "" { return nil, ErrPaymentNotFound } var payment model.PaymentOrder - if err := r.db.Where("third_order_id = ?", thirdOrderID).First(&payment).Error; err != nil { + if err := r.db.WithContext(ctx).Where("third_order_id = ?", thirdOrderID).First(&payment).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrPaymentNotFound } @@ -997,13 +997,13 @@ func (r *Repository) findPaymentForNotify(params map[string]string) (*model.Paym return &payment, nil } -func (r *Repository) findRefundPaymentForNotify(params map[string]string) (*model.PaymentOrder, error) { +func (r *Repository) findRefundPaymentForNotify(ctx context.Context, params map[string]string) (*model.PaymentOrder, error) { merchantRefundID := params["merchant_refund_id"] if merchantRefundID == "" { return nil, ErrPaymentNotFound } var payment model.PaymentOrder - if err := r.db.Where("third_order_id = ?", merchantRefundID).First(&payment).Error; err != nil { + if err := r.db.WithContext(ctx).Where("third_order_id = ?", merchantRefundID).First(&payment).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, ErrPaymentNotFound } @@ -1012,7 +1012,7 @@ func (r *Repository) findRefundPaymentForNotify(params map[string]string) (*mode return &payment, nil } -func (r *Repository) verifyNotify(payment *model.PaymentOrder, runtimeConfig *runtimePaymentConfig, params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) { +func (r *Repository) verifyNotify(ctx context.Context, payment *model.PaymentOrder, runtimeConfig *runtimePaymentConfig, params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) { var verify channelVerifyNotifyResult if runtimeConfig.isMockMode() { return verify, nil @@ -1032,7 +1032,7 @@ func (r *Repository) verifyNotify(payment *model.PaymentOrder, runtimeConfig *ru verify.ParamKeys, firstNonEmpty(verify.BaseString["notify_key"], verify.BaseString["notify_cert"]), ) - if err := r.recordNotifyDiagnostic(payment.ID, params, rawPayload, contentType, verify, "verify_failed"); err != nil { + if err := r.recordNotifyDiagnostic(ctx, payment.ID, params, rawPayload, contentType, verify, "verify_failed"); err != nil { log.Printf("[payment] %s notify diagnostic save failed payment_id=%d err=%v", runtimeConfig.Provider, payment.ID, err) } return verify, ErrPaymentVerifyFailed @@ -1231,12 +1231,12 @@ func withRawSource(raw map[string]string, source string) map[string]string { return out } -func (r *Repository) recordNotifyDiagnostic(paymentID uint64, params map[string]string, rawPayload string, contentType string, verify channelVerifyNotifyResult, status string) error { +func (r *Repository) recordNotifyDiagnostic(ctx context.Context, paymentID uint64, params map[string]string, rawPayload string, contentType string, verify channelVerifyNotifyResult, status string) error { if paymentID == 0 { return nil } raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, status) - return r.db.Model(&model.PaymentOrder{}). + return r.db.WithContext(ctx).Model(&model.PaymentOrder{}). Where("id = ?", paymentID). Update("raw_response", jsonMap(raw)).Error } diff --git a/backend/internal/modules/payment/repository_logic_test.go b/backend/internal/modules/payment/repository_logic_test.go index dac2c88..c663f91 100644 --- a/backend/internal/modules/payment/repository_logic_test.go +++ b/backend/internal/modules/payment/repository_logic_test.go @@ -246,7 +246,7 @@ func TestRuntimePaymentConfigRequiredFields(t *testing.T) { func TestServiceStartRequiresNonZeroOrderID(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.Start(1, 0, StartPaymentRequest{}, "127.0.0.1") + _, err := svc.Start(t.Context(), 1, 0, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrPaymentCannotStart) { t.Fatalf("error = %v, want ErrPaymentCannotStart", err) } @@ -255,7 +255,7 @@ func TestServiceStartRequiresNonZeroOrderID(t *testing.T) { func TestServiceStartRequiresNonZeroUserID(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.Start(0, 100, StartPaymentRequest{}, "127.0.0.1") + _, err := svc.Start(t.Context(), 0, 100, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrPaymentCannotStart) { t.Fatalf("error = %v, want ErrPaymentCannotStart", err) } @@ -264,12 +264,12 @@ func TestServiceStartRequiresNonZeroUserID(t *testing.T) { func TestServiceStartRefundRequiresPositiveAmount(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.StartRefund(100, 0, "cancel_refund", "test") + _, err := svc.StartRefund(t.Context(), 100, 0, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("error = %v, want ErrRefundCannotStart", err) } - _, err = svc.StartRefund(100, -1000, "cancel_refund", "test") + _, err = svc.StartRefund(t.Context(), 100, -1000, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("error = %v, want ErrRefundCannotStart", err) } @@ -278,7 +278,7 @@ func TestServiceStartRefundRequiresPositiveAmount(t *testing.T) { func TestServiceStartRefundRequiresNonZeroOrderID(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.StartRefund(0, 1000, "cancel_refund", "test") + _, err := svc.StartRefund(t.Context(), 0, 1000, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("error = %v, want ErrRefundCannotStart", err) } diff --git a/backend/internal/modules/payment/service.go b/backend/internal/modules/payment/service.go index 93a8f05..d2136be 100644 --- a/backend/internal/modules/payment/service.go +++ b/backend/internal/modules/payment/service.go @@ -1,6 +1,7 @@ package payment import ( + "context" "errors" "strings" ) @@ -33,81 +34,81 @@ func NewService(repo *Repository, appEnv ...string) *Service { } } -func (s *Service) Start(userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) { +func (s *Service) Start(ctx context.Context, userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if userID == 0 || orderID == 0 { return nil, ErrPaymentCannotStart } - return s.repo.Start(userID, orderID, req, clientIP) + return s.repo.Start(ctx, userID, orderID, req, clientIP) } -func (s *Service) Query(userID uint64, orderID uint64) (*PaymentDTO, error) { +func (s *Service) Query(ctx context.Context, userID uint64, orderID uint64) (*PaymentDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if userID == 0 || orderID == 0 { return nil, ErrPaymentNotFound } - return s.repo.Query(userID, orderID) + return s.repo.Query(ctx, userID, orderID) } -func (s *Service) StartWalletRecharge(userID uint64, req WalletRechargePaymentRequest, clientIP string) (*PaymentDTO, error) { +func (s *Service) StartWalletRecharge(ctx context.Context, userID uint64, req WalletRechargePaymentRequest, clientIP string) (*PaymentDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if !s.walletRechargeEnabled { return nil, ErrWalletRechargeDisabled } - return s.repo.StartWalletRecharge(userID, req, clientIP) + return s.repo.StartWalletRecharge(ctx, userID, req, clientIP) } -func (s *Service) QueryWalletRecharge(userID uint64, paymentID uint64) (*PaymentDTO, error) { +func (s *Service) QueryWalletRecharge(ctx context.Context, userID uint64, paymentID uint64) (*PaymentDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if userID == 0 || paymentID == 0 { return nil, ErrPaymentNotFound } - return s.repo.QueryWalletRecharge(userID, paymentID) + return s.repo.QueryWalletRecharge(ctx, userID, paymentID) } -func (s *Service) HandleLeshuaNotify(params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) { +func (s *Service) HandleLeshuaNotify(ctx context.Context, params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.HandleLeshuaNotify(params, rawPayload, contentType) + return s.repo.HandleLeshuaNotify(ctx, params, rawPayload, contentType) } -func (s *Service) HandleNotify(provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) { +func (s *Service) HandleNotify(ctx context.Context, provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.HandleNotify(provider, params, rawPayload, contentType, authorization) + return s.repo.HandleNotify(ctx, provider, params, rawPayload, contentType, authorization) } -func (s *Service) StartRefund(orderID uint64, refundAmountCent int64, bizType string, remark string) (*RefundDTO, error) { +func (s *Service) StartRefund(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (*RefundDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 || refundAmountCent <= 0 { return nil, ErrRefundCannotStart } - return s.repo.StartRefund(orderID, refundAmountCent, bizType, remark) + return s.repo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) } -func (s *Service) QueryRefundStatus(orderID uint64) (*RefundDTO, error) { +func (s *Service) QueryRefundStatus(ctx context.Context, orderID uint64) (*RefundDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } if orderID == 0 { return nil, ErrPaymentNotFound } - return s.repo.QueryRefundStatus(orderID) + return s.repo.QueryRefundStatus(ctx, orderID) } -func (s *Service) AdminList(query AdminPaymentQuery) (*PaginatedResult, error) { +func (s *Service) AdminList(ctx context.Context, query AdminPaymentQuery) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } @@ -120,5 +121,5 @@ func (s *Service) AdminList(query AdminPaymentQuery) (*PaginatedResult, error) { if query.PageSize > 100 { query.PageSize = 100 } - return s.repo.AdminList(query) + return s.repo.AdminList(ctx, query) } diff --git a/backend/internal/modules/payment/service_test.go b/backend/internal/modules/payment/service_test.go index 6ab4ec8..04ab749 100644 --- a/backend/internal/modules/payment/service_test.go +++ b/backend/internal/modules/payment/service_test.go @@ -8,7 +8,7 @@ import ( // TestServiceDependencyChecks 测试 Service 依赖检查 func TestServiceStartWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.Start(1, 100, StartPaymentRequest{}, "127.0.0.1") + _, err := svc.Start(t.Context(), 1, 100, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("Start() error = %v, want ErrDependencyUnavailable", err) } @@ -18,13 +18,13 @@ func TestServiceStartWithInvalidParams(t *testing.T) { svc := &Service{repo: &Repository{}} // 测试 userID 为 0 - _, err := svc.Start(0, 100, StartPaymentRequest{}, "127.0.0.1") + _, err := svc.Start(t.Context(), 0, 100, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrPaymentCannotStart) { t.Fatalf("Start() error = %v, want ErrPaymentCannotStart", err) } // 测试 orderID 为 0 - _, err = svc.Start(1, 0, StartPaymentRequest{}, "127.0.0.1") + _, err = svc.Start(t.Context(), 1, 0, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrPaymentCannotStart) { t.Fatalf("Start() error = %v, want ErrPaymentCannotStart", err) } @@ -32,7 +32,7 @@ func TestServiceStartWithInvalidParams(t *testing.T) { func TestServiceQueryWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.Query(1, 100) + _, err := svc.Query(t.Context(), 1, 100) if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("Query() error = %v, want ErrDependencyUnavailable", err) } @@ -41,7 +41,7 @@ func TestServiceQueryWithNilRepo(t *testing.T) { func TestServiceQueryWithInvalidParams(t *testing.T) { svc := &Service{repo: &Repository{}} - _, err := svc.Query(0, 100) + _, err := svc.Query(t.Context(), 0, 100) if !errors.Is(err, ErrPaymentNotFound) { t.Fatalf("Query() error = %v, want ErrPaymentNotFound", err) } @@ -49,7 +49,7 @@ func TestServiceQueryWithInvalidParams(t *testing.T) { func TestServiceStartRefundWithNilRepo(t *testing.T) { svc := &Service{repo: nil} - _, err := svc.StartRefund(100, 1000, "cancel_refund", "test") + _, err := svc.StartRefund(t.Context(), 100, 1000, "cancel_refund", "test") if !errors.Is(err, ErrDependencyUnavailable) { t.Fatalf("StartRefund() error = %v, want ErrDependencyUnavailable", err) } @@ -59,13 +59,13 @@ func TestServiceStartRefundWithInvalidParams(t *testing.T) { svc := &Service{repo: &Repository{}} // 测试 orderID 为 0 - _, err := svc.StartRefund(0, 1000, "cancel_refund", "test") + _, err := svc.StartRefund(t.Context(), 0, 1000, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("StartRefund() error = %v, want ErrRefundCannotStart", err) } // 测试金额为 0 - _, err = svc.StartRefund(100, 0, "cancel_refund", "test") + _, err = svc.StartRefund(t.Context(), 100, 0, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("StartRefund() error = %v, want ErrRefundCannotStart", err) } @@ -73,7 +73,7 @@ func TestServiceStartRefundWithInvalidParams(t *testing.T) { func TestServiceWalletRechargeDisabledInProduction(t *testing.T) { svc := NewService(&Repository{}, "production") - _, err := svc.StartWalletRecharge(1, WalletRechargePaymentRequest{AmountCent: 1000}, "127.0.0.1") + _, err := svc.StartWalletRecharge(t.Context(), 1, WalletRechargePaymentRequest{AmountCent: 1000}, "127.0.0.1") if !errors.Is(err, ErrWalletRechargeDisabled) { t.Fatalf("StartWalletRecharge() error = %v, want ErrWalletRechargeDisabled", err) } diff --git a/backend/internal/modules/systemconfig/handler.go b/backend/internal/modules/systemconfig/handler.go index a2ff679..8b50d7b 100644 --- a/backend/internal/modules/systemconfig/handler.go +++ b/backend/internal/modules/systemconfig/handler.go @@ -19,7 +19,7 @@ func NewHandler(service *Service) *Handler { } func (h *Handler) List(c *gin.Context) { - items, err := h.service.List(c.Request.Context(), ) + items, err := h.service.List(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -28,7 +28,7 @@ func (h *Handler) List(c *gin.Context) { } func (h *Handler) PublishOptions(c *gin.Context) { - options, err := h.service.PublishOptions(c.Request.Context(), ) + options, err := h.service.PublishOptions(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -37,7 +37,7 @@ func (h *Handler) PublishOptions(c *gin.Context) { } func (h *Handler) SalePriceConfig(c *gin.Context) { - config, err := h.service.SalePriceConfig(c.Request.Context(), ) + config, err := h.service.SalePriceConfig(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -46,7 +46,7 @@ func (h *Handler) SalePriceConfig(c *gin.Context) { } func (h *Handler) OrderAgreements(c *gin.Context) { - agreements, err := h.service.OrderAgreements(c.Request.Context(), ) + agreements, err := h.service.OrderAgreements(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -55,7 +55,7 @@ func (h *Handler) OrderAgreements(c *gin.Context) { } func (h *Handler) ListingPublishAgreements(c *gin.Context) { - agreements, err := h.service.ListingPublishAgreements(c.Request.Context(), ) + agreements, err := h.service.ListingPublishAgreements(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -64,7 +64,7 @@ func (h *Handler) ListingPublishAgreements(c *gin.Context) { } func (h *Handler) PostRentalNotice(c *gin.Context) { - notice, err := h.service.PostRentalNotice(c.Request.Context(), ) + notice, err := h.service.PostRentalNotice(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -73,7 +73,7 @@ func (h *Handler) PostRentalNotice(c *gin.Context) { } func (h *Handler) HomeAnnouncements(c *gin.Context) { - announcements, err := h.service.HomeAnnouncements(c.Request.Context(), ) + announcements, err := h.service.HomeAnnouncements(c.Request.Context()) if err != nil { writeConfigError(c, err) return @@ -82,7 +82,7 @@ func (h *Handler) HomeAnnouncements(c *gin.Context) { } func (h *Handler) HomeConfig(c *gin.Context) { - config, err := h.service.HomeConfig(c.Request.Context(), ) + config, err := h.service.HomeConfig(c.Request.Context()) if err != nil { writeConfigError(c, err) return diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 2134a2e..6e483e9 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -1,6 +1,7 @@ package router import ( + "context" "os" "strings" @@ -164,7 +165,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { // Inject refund function into order repo to avoid circular dependency if orderRepo != nil && paymentRepo != nil { orderRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - dto, err := paymentRepo.StartRefund(orderID, refundAmountCent, bizType, remark) + dto, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } @@ -200,7 +201,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { } if disputeRepo != nil && paymentRepo != nil { disputeRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - dto, err := paymentRepo.StartRefund(orderID, refundAmountCent, bizType, remark) + dto, err := paymentRepo.StartRefund(context.Background(), orderID, refundAmountCent, bizType, remark) if err != nil { return "", err }