From b14f52dd4a2757ce41b52cec820b5cd855a2e145 Mon Sep 17 00:00:00 2001 From: erio Date: Sat, 4 Apr 2026 20:17:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20StripeProvider=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BB=8E=E5=AE=9E=E4=BE=8B=E9=85=8D=E7=BD=AE=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E5=AF=86=E9=92=A5=EF=BC=8C=E6=97=A0=E9=9C=80=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StripeProvider 支持 instanceConfig(secretKey/publishableKey/webhookSecret) - ensureDBProviders 在环境变量缺失时从活跃实例配置注册 provider - createPaymentOrder 对 stripe 也注入实例配置 - user route 的 stripePublishableKey 从 provider 实例读取 --- src/app/api/user/route.ts | 12 +++++++++-- src/lib/order/service.ts | 3 +++ src/lib/payment/index.ts | 41 ++++++++++++++++++++++++++++++++++++++ src/lib/stripe/provider.ts | 32 ++++++++++++++++++++++------- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 5fb82f3..12b5160 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -143,8 +143,16 @@ export async function GET(request: NextRequest) { methodLimits, helpImageUrl: env.PAY_HELP_IMAGE_URL ?? null, helpText: env.PAY_HELP_TEXT ?? null, - stripePublishableKey: - enabledTypes.includes('stripe') && env.STRIPE_PUBLISHABLE_KEY ? env.STRIPE_PUBLISHABLE_KEY : null, + stripePublishableKey: (() => { + if (!enabledTypes.includes('stripe')) return null; + // 优先从注册的 StripeProvider 实例读取 + try { + const sp = paymentRegistry.getProvider('stripe' as import('@/lib/payment').PaymentType); + const pk = 'getPublishableKey' in sp ? (sp as { getPublishableKey(): string | undefined }).getPublishableKey() : undefined; + if (pk) return pk; + } catch { /* not registered */ } + return env.STRIPE_PUBLISHABLE_KEY || null; + })(), balanceDisabled, maxPendingOrders, sublabelOverrides: Object.keys(sublabelOverrides).length > 0 ? sublabelOverrides : null, diff --git a/src/lib/order/service.ts b/src/lib/order/service.ts index d0ba5bf..7b2a8fc 100644 --- a/src/lib/order/service.ts +++ b/src/lib/order/service.ts @@ -381,6 +381,9 @@ export async function createOrder(input: CreateOrderInput): Promise { @@ -107,7 +110,45 @@ export async function ensureDBProviders(): Promise { .filter(Boolean); const env = getEnv(); + + // 先用环境变量注册能注册的 registerFromList(dbProviders, env, false); + + // 对于环境变量缺失但有活跃实例的 provider,从实例配置注册 + for (const key of dbProviders) { + if (registeredKeys.has(key)) continue; + + const instance = await prisma.paymentProviderInstance.findFirst({ + where: { providerKey: key, enabled: true }, + select: { id: true, config: true }, + }); + if (!instance) continue; + + let config: Record; + try { + config = JSON.parse(decrypt(instance.config)); + } catch { + console.warn(`[payment] Failed to decrypt config for ${key} instance ${instance.id}, skipping`); + continue; + } + + switch (key) { + case 'stripe': + if (config.secretKey) { + paymentRegistry.register(new StripeProvider(instance.id, config)); + registeredKeys.add(key); + } else { + console.warn(`[payment] stripe instance ${instance.id} has no secretKey, skipping`); + } + break; + case 'easypay': + if (config.pid && config.pkey) { + paymentRegistry.register(new EasyPayProvider(instance.id, config)); + registeredKeys.add(key); + } + break; + } + } } // 注入 lazy init:Registry 方法会自动调用 initPaymentProviders()(同步回退) diff --git a/src/lib/stripe/provider.ts b/src/lib/stripe/provider.ts index 618b6ce..fe35f63 100644 --- a/src/lib/stripe/provider.ts +++ b/src/lib/stripe/provider.ts @@ -13,23 +13,41 @@ import type { } from '@/lib/payment/types'; export class StripeProvider implements PaymentProvider { - readonly name = 'stripe'; + readonly name: string; readonly providerKey = 'stripe'; readonly supportedTypes: PaymentType[] = ['stripe']; readonly defaultLimits = { stripe: { singleMax: 0, dailyMax: 0 }, // 0 = unlimited }; + readonly instanceId?: string; + private instanceConfig?: Record; private client: Stripe | null = null; + constructor(instanceId?: string, instanceConfig?: Record) { + this.instanceId = instanceId; + this.instanceConfig = instanceConfig; + this.name = instanceId ? `stripe:${instanceId}` : 'stripe'; + } + private getClient(): Stripe { if (this.client) return this.client; - const env = getEnv(); - if (!env.STRIPE_SECRET_KEY) throw new Error('STRIPE_SECRET_KEY not configured'); - this.client = new Stripe(env.STRIPE_SECRET_KEY); + const secretKey = this.instanceConfig?.secretKey || getEnv().STRIPE_SECRET_KEY; + if (!secretKey) throw new Error('STRIPE_SECRET_KEY not configured'); + this.client = new Stripe(secretKey); return this.client; } + /** 获取 publishable key(实例配置优先,回退到环境变量) */ + getPublishableKey(): string | undefined { + return this.instanceConfig?.publishableKey || getEnv().STRIPE_PUBLISHABLE_KEY || undefined; + } + + /** 获取 webhook secret(实例配置优先,回退到环境变量) */ + private getWebhookSecret(): string | undefined { + return this.instanceConfig?.webhookSecret || getEnv().STRIPE_WEBHOOK_SECRET || undefined; + } + async createPayment(request: CreatePaymentRequest): Promise { const stripe = this.getClient(); @@ -72,14 +90,14 @@ export class StripeProvider implements PaymentProvider { headers: Record, ): Promise { const stripe = this.getClient(); - const env = getEnv(); - if (!env.STRIPE_WEBHOOK_SECRET) throw new Error('STRIPE_WEBHOOK_SECRET not configured'); + const webhookSecret = this.getWebhookSecret(); + if (!webhookSecret) throw new Error('STRIPE_WEBHOOK_SECRET not configured'); const sig = headers['stripe-signature'] || ''; const event = stripe.webhooks.constructEvent( typeof rawBody === 'string' ? Buffer.from(rawBody) : rawBody, sig, - env.STRIPE_WEBHOOK_SECRET, + webhookSecret, ); if (event.type === 'payment_intent.succeeded') {