export type BillingPlanId = "starter" | "basic" | "pro" | "ultimate"; export type PaidBillingPlanId = Exclude; export type BillingPlanDefinition = { id: BillingPlanId; name: string; price: string; period: string; monthlyImageLimit: number | null; cnyFen: number; }; export const BILLING_PLAN_DEFINITIONS: BillingPlanDefinition[] = [ { id: "starter", name: "Starter", price: "¥0", period: "/month", monthlyImageLimit: 200, cnyFen: 0, }, { id: "basic", name: "Basic", price: "¥69", period: "/month", monthlyImageLimit: 1200, cnyFen: 6900, }, { id: "pro", name: "Pro", price: "¥199", period: "/month", monthlyImageLimit: 6000, cnyFen: 19900, }, { id: "ultimate", name: "Ultimate", price: "¥599", period: "/month", monthlyImageLimit: null, cnyFen: 59900, }, ]; const PAID_PLAN_IDS: PaidBillingPlanId[] = ["basic", "pro", "ultimate"]; const STRIPE_PRICE_ENV: Record = { basic: "STRIPE_PRICE_ID_BASIC", pro: "STRIPE_PRICE_ID_PRO", ultimate: "STRIPE_PRICE_ID_ULTIMATE", }; export function isBillingPlanId(value: string): value is BillingPlanId { return BILLING_PLAN_DEFINITIONS.some((plan) => plan.id === value); } export function isPaidBillingPlanId(value: string): value is PaidBillingPlanId { return PAID_PLAN_IDS.includes(value as PaidBillingPlanId); } export function getBillingPlanById(planId: BillingPlanId) { return BILLING_PLAN_DEFINITIONS.find((plan) => plan.id === planId) || null; } export function getPaidPlanById(planId: PaidBillingPlanId) { return BILLING_PLAN_DEFINITIONS.find((plan) => plan.id === planId) || null; } export function getPaidPlanAmountFen(planId: PaidBillingPlanId) { return getPaidPlanById(planId)?.cnyFen ?? 0; } export function getMonthlyImageLimitByPlanId(planId: BillingPlanId) { return getBillingPlanById(planId)?.monthlyImageLimit ?? null; } export function resolvePlanByMonthlyImageLimit(limit: number | null | undefined): BillingPlanDefinition { const ultimatePlan = BILLING_PLAN_DEFINITIONS.find((plan) => plan.id === "ultimate") || BILLING_PLAN_DEFINITIONS[0]; if (limit == null) { return ultimatePlan; } const exactMatch = BILLING_PLAN_DEFINITIONS.find( (plan) => plan.monthlyImageLimit != null && plan.monthlyImageLimit === limit ); if (exactMatch) { return exactMatch; } const sortedPlans = BILLING_PLAN_DEFINITIONS.filter((plan) => plan.monthlyImageLimit != null).sort( (a, b) => (a.monthlyImageLimit ?? 0) - (b.monthlyImageLimit ?? 0) ); for (const plan of sortedPlans) { if ((plan.monthlyImageLimit ?? 0) >= limit) { return plan; } } return ultimatePlan; } export function getStripePriceIdForPlan(planId: PaidBillingPlanId) { const envName = STRIPE_PRICE_ENV[planId]; const value = process.env[envName]; return value && value.trim().length > 0 ? value.trim() : null; } export function getCheckoutEnabledPlans() { return PAID_PLAN_IDS.reduce>((acc, planId) => { acc[planId] = Boolean(getStripePriceIdForPlan(planId)); return acc; }, { basic: false, pro: false, ultimate: false, }); } function getEnv(name: string) { const value = process.env[name]; return value && value.trim().length > 0 ? value.trim() : null; } export function getWechatPayConfig() { const appId = getEnv("WECHAT_PAY_APP_ID"); const mchId = getEnv("WECHAT_PAY_MCH_ID"); const privateKey = getEnv("WECHAT_PAY_PRIVATE_KEY"); const serialNo = getEnv("WECHAT_PAY_SERIAL_NO"); const apiV3Key = getEnv("WECHAT_PAY_API_V3_KEY"); const platformPublicKey = getEnv("WECHAT_PAY_PLATFORM_PUBLIC_KEY"); if (!appId || !mchId || !privateKey || !serialNo || !apiV3Key || !platformPublicKey) { return null; } return { appId, mchId, privateKey, serialNo, apiV3Key, platformPublicKey, notifyUrl: getEnv("WECHAT_PAY_NOTIFY_URL"), }; } export function isWechatPayEnabled() { return Boolean(getWechatPayConfig()); }