import { PLAN_CATALOG } from '../billing-subscription.mjs'; export function attachPortalBillingRoutes({ app, jsonBody, userAuthReady = Promise.resolve(), getUserAuth = () => null, getRechargeService = () => null, getSubscriptionService = () => null, getMindSpace = () => null, userToken, planCatalogFallback = PLAN_CATALOG, } = {}) { if ( !app || typeof jsonBody !== 'function' || typeof userToken !== 'function' ) { throw new Error( 'attachPortalBillingRoutes requires route dependencies', ); } app.get( '/auth/billing/ledger', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const limit = Math.min( Math.max( Number(req.query?.limit) || 30, 1, ), 100, ); const entries = await userAuth.listBillingLedger({ userId: me.id, limit, types: [ 'recharge', 'adjust', 'refund', ], }); res.json({ entries }); }, ); app.get( '/auth/billing/config', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const rechargeService = getRechargeService(); if (!userAuth || !rechargeService) { return res.status(503).json({ message: '未启用计费系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const subscriptionService = getSubscriptionService(); const [config, sub] = await Promise.all([ rechargeService.getBillingConfig(me.id), subscriptionService ? subscriptionService.getActiveSubscription( me.id, ) : null, ]); return res.json({ ...config, subscription: sub, }); }, ); app.get( '/auth/billing/subscription', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const subscriptionService = getSubscriptionService(); const sub = subscriptionService ? await subscriptionService.getActiveSubscription( me.id, ) : null; const planCatalog = subscriptionService?._planCatalogService; const plans = !sub && planCatalog ? await planCatalog.listPlans({ includeInactive: false, }) : sub ? undefined : planCatalogFallback; return res.json({ subscription: sub, plans, }); }, ); app.get( '/auth/billing/plans', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const subscriptionService = getSubscriptionService(); const planCatalog = subscriptionService?._planCatalogService; const plans = planCatalog ? ( await planCatalog.listPlans({ includeInactive: false, }) ) .filter((plan) => plan.priceCents > 0) .map((plan) => ({ key: plan.planType, ...plan, })) : Object.entries(planCatalogFallback) .filter( ([, plan]) => plan.priceCents > 0, ) .map(([key, plan]) => ({ key, ...plan, })); const [sub, wallet] = await Promise.all([ subscriptionService ? subscriptionService.getActiveSubscription( me.id, ) : null, userAuth.getUserById(me.id), ]); return res.json({ plans, subscription: sub, balanceCents: wallet ? Number(wallet.balance_cents ?? 0) : 0, }); }, ); app.post( '/auth/billing/subscribe', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const subscriptionService = getSubscriptionService(); if (!userAuth || !subscriptionService) { return res.status(503).json({ message: '未启用订阅系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } if (me.status === 'disabled') { return res.status(403).json({ message: '账户已禁用', }); } const { planType, autoRenew = false, } = req.body ?? {}; if ( !planType || typeof planType !== 'string' ) { return res.status(400).json({ message: '请选择套餐', }); } const result = await subscriptionService.purchaseSubscription( me.id, planType, Boolean(autoRenew), ); if (!result.ok) { if ( result.code === 'INSUFFICIENT_BALANCE' ) { return res.status(402).json({ message: result.message, code: result.code, balanceCents: result.balanceCents, requiredCents: result.requiredCents, shortfallCents: result.shortfallCents, }); } if ( result.code === 'DOWNGRADE_NOT_ALLOWED' ) { return res.status(409).json({ message: result.message, code: result.code, currentPlanType: result.currentPlanType, }); } return res.status(400).json({ message: result.message, }); } return res.json({ subscription: result.subscription, balanceCents: result.balanceCents, }); }, ); app.post( '/auth/billing/space-purchase', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const mindSpace = getMindSpace(); if (!userAuth || !mindSpace) { return res.status(503).json({ message: '未启用空间系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const sizeMb = Number(req.body?.sizeMb); const result = await userAuth.purchaseSpaceQuota( me.id, sizeMb, ); if (!result.ok) { if ( result.code === 'INSUFFICIENT_BALANCE' ) { return res.status(402).json({ message: result.message, code: result.code, details: { code: 'INSUFFICIENT_BALANCE', balanceCents: result.balanceCents, minRechargeCents: result.minRechargeCents, suggestedTiers: result.suggestedTiers, }, }); } return res.status(400).json({ message: result.message, }); } const quota = await mindSpace.getQuota(me.id); return res.json({ quota: quota ?? result.quota, balanceCents: result.balanceCents, purchasedMb: sizeMb, costCents: sizeMb * 200, }); }, ); app.post( '/auth/billing/auto-renew', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const subscriptionService = getSubscriptionService(); if (!userAuth || !subscriptionService) { return res.status(503).json({ message: '未启用订阅系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const { enabled } = req.body ?? {}; if (typeof enabled !== 'boolean') { return res.status(400).json({ message: '请传入 enabled: true/false', }); } const result = await subscriptionService.setAutoRenew( me.id, enabled, ); return res.json(result); }, ); app.post( '/auth/billing/recharge-orders', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const rechargeService = getRechargeService(); if (!userAuth || !rechargeService) { return res.status(503).json({ message: '未启用计费系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const amountCents = Number( req.body?.amountCents, ); const payScene = [ 'native', 'h5', 'jsapi', ].includes(req.body?.payScene) ? req.body.payScene : 'native'; const result = await rechargeService.createOrder({ userId: me.id, amountCents, payScene, clientIp: req.ip, }); if (!result.ok) { return res.status(400).json({ message: result.message, }); } return res.status(201).json({ order: result.order, }); }, ); app.get( '/auth/billing/recharge-orders/:orderId', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const rechargeService = getRechargeService(); if (!userAuth || !rechargeService) { return res.status(503).json({ message: '未启用计费系统', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } const order = await rechargeService.getOrderForUser( me.id, req.params.orderId, ); if (!order) { return res.status(404).json({ message: '订单不存在', }); } let balanceCents = null; if (order.status === 'paid') { const user = await userAuth.getUserById( me.id, ); balanceCents = user ? Number(user.balance_cents ?? 0) : null; } return res.json({ order, balanceCents, }); }, ); }