From 82cc410cdf6c386de11c06e75df1c215aedf8ca4 Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 7 Apr 2026 03:20:26 +0800 Subject: [PATCH] fix(payment): fix order creation + show actual provider types on payment page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix CreateOrderRequest field name mismatch (payment_type → paymentType) - Payment page now discovers available types from providers (not global config) - Backend GetLimits returns map keyed by payment type - EasyPay shows 3 buttons (跳转/支付宝/微信), Stripe shows card/alipay/wxpay/link - Auto-select first method after limits loaded --- backend/cmd/server/VERSION | 2 +- backend/internal/handler/payment_handler.go | 14 ++++----- .../service/payment_config_service.go | 30 +++++++++++++++++++ frontend/src/types/payment.ts | 6 ++-- frontend/src/views/user/PaymentView.vue | 11 +++---- 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index 05528b437f..aa56265567 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.108.61 +0.1.108.62 diff --git a/backend/internal/handler/payment_handler.go b/backend/internal/handler/payment_handler.go index 6172dae7b0..c3acd6d6ae 100644 --- a/backend/internal/handler/payment_handler.go +++ b/backend/internal/handler/payment_handler.go @@ -61,20 +61,20 @@ func (h *PaymentHandler) GetChannels(c *gin.Context) { response.Success(c, channels) } -// GetLimits returns per-payment-type limits. +// GetLimits returns per-payment-type limits derived from enabled provider instances. // GET /api/v1/payment/limits func (h *PaymentHandler) GetLimits(c *gin.Context) { - cfg, err := h.configService.GetPaymentConfig(c.Request.Context()) + limits, err := h.configService.GetAvailableMethodLimits(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return } - limits, err := h.configService.GetMethodLimits(c.Request.Context(), cfg.EnabledTypes) - if err != nil { - response.ErrorFrom(c, err) - return + // Return as map keyed by payment type (frontend expects Record) + result := make(map[string]service.MethodLimits, len(limits)) + for _, ml := range limits { + result[ml.PaymentType] = ml } - response.Success(c, limits) + response.Success(c, result) } // CreateOrderRequest is the request body for creating a payment order. diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go index 674a8e8190..dc29204c5f 100644 --- a/backend/internal/service/payment_config_service.go +++ b/backend/internal/service/payment_config_service.go @@ -606,6 +606,36 @@ func (s *PaymentConfigService) GetPlan(ctx context.Context, id int64) (*dbent.Su return plan, nil } +// GetAvailableMethodLimits collects all payment types from enabled provider +// instances and returns limits for each. This is used by the user-facing payment +// page to discover which payment methods are actually available. +func (s *PaymentConfigService) GetAvailableMethodLimits(ctx context.Context) ([]MethodLimits, error) { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Where(paymentproviderinstance.EnabledEQ(true)).All(ctx) + if err != nil { + return nil, fmt.Errorf("query provider instances: %w", err) + } + // Collect unique payment types from all enabled providers + typeSet := make(map[string]bool) + for _, inst := range instances { + for _, t := range splitTypes(inst.SupportedTypes) { + typeSet[t] = true + } + } + result := make([]MethodLimits, 0, len(typeSet)) + for pt := range typeSet { + ml := MethodLimits{PaymentType: pt} + for _, inst := range instances { + if !payment.InstanceSupportsType(inst.SupportedTypes, pt) { + continue + } + pcApplyInstanceLimits(inst, pt, &ml) + } + result = append(result, ml) + } + return result, nil +} + // GetMethodLimits returns per-payment-type limits from enabled provider instances. func (s *PaymentConfigService) GetMethodLimits(ctx context.Context, types []string) ([]MethodLimits, error) { instances, err := s.entClient.PaymentProviderInstance.Query(). diff --git a/frontend/src/types/payment.ts b/frontend/src/types/payment.ts index 191fad23e8..e0156da838 100644 --- a/frontend/src/types/payment.ts +++ b/frontend/src/types/payment.ts @@ -114,9 +114,9 @@ export interface ProviderInstance { export interface CreateOrderRequest { amount: number - payment_type: string - order_type: string - plan_id?: number + paymentType: string + orderType: string + planId?: number } export interface CreateOrderResult { diff --git a/frontend/src/views/user/PaymentView.vue b/frontend/src/views/user/PaymentView.vue index 7e5230415e..2ae9da6cac 100644 --- a/frontend/src/views/user/PaymentView.vue +++ b/frontend/src/views/user/PaymentView.vue @@ -156,7 +156,8 @@ const tabs = computed(() => { return result }) -const enabledMethods = computed(() => config.value?.enabled_payment_types ?? []) +// Available methods derived from limits API (actual provider types) +const enabledMethods = computed(() => Object.keys(methodLimits.value)) // 0 = no limit; provider-level overrides global const minAmount = computed(() => { const limit = methodLimits.value[selectedMethod.value] @@ -225,9 +226,9 @@ async function createOrder(orderAmount: number, orderType: string, planId?: numb try { const result = await paymentStore.createOrder({ amount: orderAmount, - payment_type: selectedMethod.value, - order_type: orderType, - plan_id: planId, + paymentType: selectedMethod.value, + orderType: orderType, + planId: planId, }) if (result.client_secret) { router.push({ path: '/payment/stripe', query: { order_id: String(result.order_id), client_secret: result.client_secret } }) @@ -260,11 +261,11 @@ watch(() => activeTab.value, (tab) => { onMounted(async () => { try { await paymentStore.fetchConfig(true) - if (enabledMethods.value.length) selectedMethod.value = enabledMethods.value[0] try { const limitsRes = await paymentAPI.getLimits() methodLimits.value = limitsRes.data } catch (e) { /* limits endpoint may not exist */ } + if (enabledMethods.value.length) selectedMethod.value = enabledMethods.value[0] if (config.value?.balance_disabled) { activeTab.value = 'subscription' await loadPlans()