diff --git a/backend/internal/e2e/rental_flow_test.go b/backend/internal/e2e/rental_flow_test.go index 17c625e..e940041 100644 --- a/backend/internal/e2e/rental_flow_test.go +++ b/backend/internal/e2e/rental_flow_test.go @@ -204,24 +204,27 @@ type flowServices struct { func newFlowServices(db *gorm.DB) flowServices { listingRepo := listing.NewRepository(db) - orderRepo := order.NewRepository(db) walletRepo := wallet.NewRepository(db) configRepo := paymentconfig.NewRepository(db, &paymentconfig.MockEncryptor{}) - paymentRepo := payment.NewRepository(db, configRepo, orderRepo, walletRepo) - disputeRepo := dispute.NewRepository(db) - orderRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - refund, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) - if err != nil { - return "", err - } - return refund.Status, nil + var paymentRepo *payment.Repository + orderRepo := order.NewRepository(db, order.Dependencies{ + RefundStarter: order.RefundStarterFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + refund, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) + if err != nil { + return "", err + } + return refund.Status, nil + }), }) - disputeRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - refund, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) - if err != nil { - return "", err - } - return refund.Status, nil + paymentRepo = payment.NewRepository(db, configRepo, orderRepo, walletRepo) + disputeRepo := dispute.NewRepository(db, dispute.Dependencies{ + RefundStarter: dispute.RefundStarterFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + refund, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) + if err != nil { + return "", err + } + return refund.Status, nil + }), }) return flowServices{ diff --git a/backend/internal/modules/dispute/arbitration.go b/backend/internal/modules/dispute/arbitration.go index 8047241..c91bc0f 100644 --- a/backend/internal/modules/dispute/arbitration.go +++ b/backend/internal/modules/dispute/arbitration.go @@ -256,7 +256,7 @@ func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, b if amountCent <= 0 { return nil, nil } - if r.refundFunc == nil { + if r.refundStarter == nil { return nil, ErrDependencyUnavailable } order.RefundStatus = "pending" @@ -271,10 +271,10 @@ func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, b } func (r *Repository) startRefundBestEffort(ctx context.Context, action *refundAction) { - if action == nil || r.refundFunc == nil { + if action == nil || r.refundStarter == nil { return } - _, _ = r.refundFunc(ctx, action.OrderID, action.RefundAmountCent, action.BizType, action.Remark) + _, _ = r.refundStarter.StartRefund(ctx, action.OrderID, action.RefundAmountCent, action.BizType, action.Remark) } func renterFrozenBalance(tx *gorm.DB, renterID uint64) (int64, error) { diff --git a/backend/internal/modules/dispute/repository.go b/backend/internal/modules/dispute/repository.go index b9319e3..8c0b8bc 100644 --- a/backend/internal/modules/dispute/repository.go +++ b/backend/internal/modules/dispute/repository.go @@ -8,14 +8,24 @@ import ( ) type Repository struct { - db *gorm.DB - refundFunc RefundFunc + db *gorm.DB + refundStarter RefundStarter } -// RefundFunc 由 payment 模块注入,避免 dispute 与 payment 形成循环依赖。 +// RefundStarter 由支付模块适配实现,避免 dispute 直接依赖 payment。 +type RefundStarter interface { + StartRefund(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) +} -// RefundFunc 由 payment 模块注入,避免 dispute 与 payment 形成循环依赖。 -type RefundFunc func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) +type RefundStarterFunc func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) + +func (fn RefundStarterFunc) StartRefund(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + return fn(ctx, orderID, refundAmountCent, bizType, remark) +} + +type Dependencies struct { + RefundStarter RefundStarter +} type refundAction struct { OrderID uint64 @@ -24,12 +34,12 @@ type refundAction struct { Remark string } -func NewRepository(db *gorm.DB) *Repository { - return &Repository{db: db} -} - -func (r *Repository) SetRefundFunc(fn RefundFunc) { - r.refundFunc = fn +func NewRepository(db *gorm.DB, deps ...Dependencies) *Repository { + repo := &Repository{db: db} + if len(deps) > 0 { + repo.refundStarter = deps[0].RefundStarter + } + return repo } func IsNotFound(err error) bool { diff --git a/backend/internal/modules/order/admin_actions.go b/backend/internal/modules/order/admin_actions.go index a32c879..ce15c79 100644 --- a/backend/internal/modules/order/admin_actions.go +++ b/backend/internal/modules/order/admin_actions.go @@ -172,14 +172,14 @@ func (r *Repository) AdminRefund(ctx context.Context, orderID uint64) (*RefundSt if order.RefundStatus == refundStatusRefunded { return r.buildRefundStatusDTO(&order), nil } - if r.refundFunc == nil { + if r.refundStarter == nil { return nil, ErrDependencyUnavailable } totalCent := order.RentAmountCent + order.DepositAmountCent if totalCent <= 0 { return nil, ErrInvalidCheckoutAmount } - status, err := r.refundFunc(ctx, orderID, totalCent, refundBizAdmin, "后台人工退款") + status, err := r.refundStarter.StartRefund(ctx, orderID, totalCent, refundBizAdmin, "后台人工退款") if err != nil { return nil, err } diff --git a/backend/internal/modules/order/lifecycle.go b/backend/internal/modules/order/lifecycle.go index 7c5055e..5af637e 100644 --- a/backend/internal/modules/order/lifecycle.go +++ b/backend/internal/modules/order/lifecycle.go @@ -195,8 +195,8 @@ func (r *Repository) ConfirmPaidFromChannel(ctx context.Context, orderID uint64, if err != nil { return err } - if newConvID > 0 && r.chatRepo != nil { - r.chatRepo.NotifyNewConversation(newConvID) + if newConvID > 0 && r.chatNotifier != nil { + r.chatNotifier.NotifyNewConversation(newConvID) } return nil } diff --git a/backend/internal/modules/order/refund.go b/backend/internal/modules/order/refund.go index 2bbdea5..8a03c71 100644 --- a/backend/internal/modules/order/refund.go +++ b/backend/internal/modules/order/refund.go @@ -11,7 +11,7 @@ func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, b if amountCent <= 0 { return nil, nil } - if r.refundFunc == nil { + if r.refundStarter == nil { return nil, ErrDependencyUnavailable } order.RefundStatus = refundStatusPending @@ -26,10 +26,10 @@ func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, b } func (r *Repository) startRefundBestEffort(ctx context.Context, action *refundAction) { - if action == nil || r.refundFunc == nil { + if action == nil || r.refundStarter == nil { return } - if _, err := r.refundFunc(ctx, action.OrderID, action.RefundAmountCent, action.BizType, action.Remark); err != nil { + if _, err := r.refundStarter.StartRefund(ctx, action.OrderID, action.RefundAmountCent, action.BizType, action.Remark); err != nil { log.Printf("[order] start refund failed order_id=%d biz_type=%s amount_cent=%d err=%v", action.OrderID, action.BizType, action.RefundAmountCent, err) } } diff --git a/backend/internal/modules/order/repository.go b/backend/internal/modules/order/repository.go index b419709..f66f9fe 100644 --- a/backend/internal/modules/order/repository.go +++ b/backend/internal/modules/order/repository.go @@ -3,13 +3,29 @@ package order import ( "context" - "hfb_sys/backend/internal/modules/chat" - "gorm.io/gorm" ) -// RefundFunc 由 payment 模块注入,避免 order 与 payment 形成循环依赖。 -type RefundFunc func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) +// RefundStarter 由支付模块适配实现,避免 order 直接依赖 payment。 +type RefundStarter interface { + StartRefund(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) +} + +type RefundStarterFunc func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error) + +func (fn RefundStarterFunc) StartRefund(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + return fn(ctx, orderID, refundAmountCent, bizType, remark) +} + +type OrderChatNotifier interface { + NotifyNewConversation(conversationID uint64) +} + +type Dependencies struct { + ChatNotifier OrderChatNotifier + RefundStarter RefundStarter +} + type refundAction struct { OrderID uint64 RefundAmountCent int64 @@ -18,21 +34,18 @@ type refundAction struct { } type Repository struct { - db *gorm.DB - chatRepo *chat.Repository - refundFunc RefundFunc + db *gorm.DB + chatNotifier OrderChatNotifier + refundStarter RefundStarter } const defaultPendingPaymentTimeoutMinutes = 15 -func NewRepository(db *gorm.DB) *Repository { - return &Repository{db: db} -} - -func (r *Repository) SetChatRepo(cr *chat.Repository) { - r.chatRepo = cr -} - -func (r *Repository) SetRefundFunc(fn RefundFunc) { - r.refundFunc = fn +func NewRepository(db *gorm.DB, deps ...Dependencies) *Repository { + repo := &Repository{db: db} + if len(deps) > 0 { + repo.chatNotifier = deps[0].ChatNotifier + repo.refundStarter = deps[0].RefundStarter + } + return repo } diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 494945c..4b23461 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -109,9 +109,30 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { if deps.DB != nil { listingRepo = listing.NewRepository(deps.DB) } + var chatHub *chathub.Hub + if deps.DB != nil { + chatHub = chathub.NewHub(deps.DB) + } + var chatRepo *chat.Repository + if deps.DB != nil { + chatRepo = chat.NewRepository(deps.DB, chatHub) + } + var paymentRepo *payment.Repository var orderRepo *order.Repository if deps.DB != nil { - orderRepo = order.NewRepository(deps.DB) + orderRepo = order.NewRepository(deps.DB, order.Dependencies{ + ChatNotifier: chatRepo, + RefundStarter: order.RefundStarterFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + if paymentRepo == nil { + return "", order.ErrDependencyUnavailable + } + dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) + if err != nil { + return "", err + } + return dto.Status, nil + }), + }) } orderService := order.NewService(orderRepo) orderHandler := order.NewHandler(orderService) @@ -156,56 +177,36 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { paymentConfigHandler = paymentconfig.NewHandler(paymentConfigService) } - var paymentRepo *payment.Repository if deps.DB != nil { paymentRepo = payment.NewRepository(deps.DB, paymentConfigRepo, orderRepo, walletRepo) } paymentService := payment.NewService(paymentRepo, cfg.AppEnv) paymentHandler := payment.NewHandler(paymentService) - // Inject refund function into order repo to avoid circular dependency - if orderRepo != nil && paymentRepo != nil { - orderRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) - if err != nil { - return "", err - } - return dto.Status, nil - }) - } var notificationRepo *notification.Repository if deps.DB != nil { notificationRepo = notification.NewRepository(deps.DB) } notificationService := notification.NewService(notificationRepo) notificationHandler := notification.NewHandler(notificationService) - var chatHub *chathub.Hub - if deps.DB != nil { - chatHub = chathub.NewHub(deps.DB) - } - var chatRepo *chat.Repository - if deps.DB != nil { - chatRepo = chat.NewRepository(deps.DB, chatHub) - } chatService := chat.NewService(chatRepo) chatHandler := chat.NewHandler(chatService) var chatHubHandler *chathub.Handler if chatHub != nil { chatHubHandler = chathub.NewHandler(chatHub) } - if orderRepo != nil && chatRepo != nil { - orderRepo.SetChatRepo(chatRepo) - } var disputeRepo *dispute.Repository if deps.DB != nil { - disputeRepo = dispute.NewRepository(deps.DB) - } - if disputeRepo != nil && paymentRepo != nil { - disputeRepo.SetRefundFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { - dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) - if err != nil { - return "", err - } - return dto.Status, nil + disputeRepo = dispute.NewRepository(deps.DB, dispute.Dependencies{ + RefundStarter: dispute.RefundStarterFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { + if paymentRepo == nil { + return "", dispute.ErrDependencyUnavailable + } + dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) + if err != nil { + return "", err + } + return dto.Status, nil + }), }) } disputeService := dispute.NewService(disputeRepo)