From 1455ade5cd1331dc5f84b199d0ff06c17cff00ea Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 7 Apr 2026 11:45:49 +0800 Subject: [PATCH] fix(payment): use selected instance config for CreatePayment Root cause: invokeProvider used the registry's provider (created once at startup from an arbitrary instance) instead of the selected instance's config. This meant payments always used one instance's credentials regardless of which instance was selected. Fix: create a fresh provider from the selected instance's decrypted config for each payment call, ensuring correct credentials (PID, PKey, CID, Stripe keys, etc.). Also fix SelectInstance for Stripe: when paymentType equals providerKey (e.g. "stripe"), all instances of that provider are candidates. The DB stores sub-types (card,alipay,wxpay,link) not "stripe" itself. --- backend/internal/payment/load_balancer.go | 6 ++++-- backend/internal/service/payment_service.go | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/backend/internal/payment/load_balancer.go b/backend/internal/payment/load_balancer.go index 68d6bd8e28..0c80254482 100644 --- a/backend/internal/payment/load_balancer.go +++ b/backend/internal/payment/load_balancer.go @@ -59,10 +59,12 @@ func (lb *DefaultLoadBalancer) SelectInstance(ctx context.Context, providerKey s return nil, fmt.Errorf("query provider instances: %w", err) } - // Filter by supported types + // Filter by supported types. + // When paymentType equals providerKey (e.g. "stripe"), all instances of that + // provider are candidates — the sub-type filtering is handled internally. var candidates []*dbent.PaymentProviderInstance for _, inst := range instances { - if InstanceSupportsType(inst.SupportedTypes, paymentType) { + if paymentType == providerKey || InstanceSupportsType(inst.SupportedTypes, paymentType) { candidates = append(candidates, inst) } } diff --git a/backend/internal/service/payment_service.go b/backend/internal/service/payment_service.go index 455cca27ed..5258fa7a58 100644 --- a/backend/internal/service/payment_service.go +++ b/backend/internal/service/payment_service.go @@ -324,22 +324,30 @@ func (s *PaymentService) checkDailyLimit(ctx context.Context, tx *dbent.Tx, user func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.PaymentOrder, req CreateOrderRequest, cfg *PaymentConfig, payAmountStr string, payAmount float64, plan *dbent.SubscriptionPlan) (*CreateOrderResponse, error) { s.EnsureProviders(ctx) - provider, err := s.registry.GetProvider(req.PaymentType) - if err != nil { + // Look up the provider key from registry (validates the payment type is supported) + providerKey := s.registry.GetProviderKey(req.PaymentType) + if providerKey == "" { return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment method (%s) is not configured", req.PaymentType)) } - sel, err := s.loadBalancer.SelectInstance(ctx, provider.ProviderKey(), req.PaymentType) + sel, err := s.loadBalancer.SelectInstance(ctx, providerKey, req.PaymentType) if err != nil { return nil, fmt.Errorf("select provider instance: %w", err) } if sel == nil { return nil, infraerrors.TooManyRequests("NO_AVAILABLE_INSTANCE", "no available payment instance") } - subject := s.buildPaymentSubject(plan, payAmountStr, cfg) - pr, err := provider.CreatePayment(ctx, payment.CreatePaymentRequest{OrderID: formatOrderID(order.ID), Amount: payAmountStr, PaymentType: req.PaymentType, Subject: subject, ClientIP: req.ClientIP, IsMobile: req.IsMobile, InstanceSubMethods: sel.SupportedTypes}) + // Create a fresh provider from the selected instance's config, + // so each payment uses the correct instance credentials. + prov, err := provider.CreateProvider(providerKey, sel.InstanceID, sel.Config) if err != nil { return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", "payment method is temporarily unavailable") } + subject := s.buildPaymentSubject(plan, payAmountStr, cfg) + pr, err := prov.CreatePayment(ctx, payment.CreatePaymentRequest{OrderID: formatOrderID(order.ID), Amount: payAmountStr, PaymentType: req.PaymentType, Subject: subject, ClientIP: req.ClientIP, IsMobile: req.IsMobile, InstanceSubMethods: sel.SupportedTypes}) + if err != nil { + slog.Error("[PaymentService] CreatePayment failed", "provider", providerKey, "instance", sel.InstanceID, "error", err) + return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", "payment method is temporarily unavailable") + } _, err = s.entClient.PaymentOrder.UpdateOneID(order.ID).SetNillablePaymentTradeNo(psNilIfEmpty(pr.TradeNo)).SetNillablePayURL(psNilIfEmpty(pr.PayURL)).SetNillableQrCode(psNilIfEmpty(pr.QRCode)).SetNillableProviderInstanceID(psNilIfEmpty(sel.InstanceID)).Save(ctx) if err != nil { return nil, fmt.Errorf("update order with payment details: %w", err)