import Stripe from 'stripe'; import { loadBillingConfig, SubscriptionTier } from '../config/billing.config'; import type { Subscription, SubscriptionCreateSchema, SubscriptionUpdateSchema } from '../models/subscription.model'; const config = loadBillingConfig(); const stripe = new Stripe(config.stripe.apiKey, { apiVersion: '2024-04-10' }); export class BillingService { private static instance: BillingService; private constructor() {} static getInstance(): BillingService { if (!BillingService.instance) { BillingService.instance = new BillingService(); } return BillingService.instance; } async createCustomer(email: string, userId: string): Promise { const customer = await stripe.customers.create({ email, metadata: { userId }, }); return customer; } async getCustomer(customerId: string): Promise { try { const customer = await stripe.customers.retrieve(customerId); return customer as Stripe.Customer; } catch { return null; } } async createSubscription( userId: string, tier: SubscriptionTier, customerId: string ): Promise<{ subscription: Stripe.Subscription; customer: Stripe.Customer }> { const tierConfig = config.tiers[tier]; const subscription = await stripe.subscriptions.create({ customer: customerId, items: [{ price: tierConfig.priceId }], metadata: { userId, tier }, }); const customer = await this.getCustomer(customerId); return { subscription, customer: customer! }; } async cancelSubscription( subscriptionId: string, cancelAtPeriodEnd: boolean = false ): Promise { if (cancelAtPeriodEnd) { return await stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true, }); } return await stripe.subscriptions.cancel(subscriptionId); } async updateSubscription( subscriptionId: string, newTier: SubscriptionTier ): Promise { const newTierConfig = config.tiers[newTier]; const subscription = await stripe.subscriptions.retrieve(subscriptionId); const updated = await stripe.subscriptions.update(subscriptionId, { proration_behavior: 'create_prorations', items: [ { id: subscription.items.data[0]?.id, price: newTierConfig.priceId, }, ], }); return updated; } async createCustomerPortalSession( customerId: string, returnUrl: string ): Promise { return await stripe.billingPortal.sessions.create({ customer: customerId, return_url: returnUrl, }); } async getSubscription(subscriptionId: string): Promise { try { const subscription = await stripe.subscriptions.retrieve(subscriptionId); return subscription; } catch { return null; } } async getTierLimits(tier: SubscriptionTier) { return config.tiers[tier]; } async checkUsageAgainstLimit( userId: string, tier: SubscriptionTier, currentUsage: number ): Promise<{ withinLimit: boolean; remaining: number; limit: number }> { const tierConfig = config.tiers[tier]; const limit = tierConfig.callMinutesLimit; const remaining = Math.max(0, limit - currentUsage); return { withinLimit: currentUsage <= limit, remaining, limit, }; } async createInvoice( customerId: string, amount: number, description: string, metadata?: Record ): Promise { return await stripe.invoices.create({ customer: customerId, metadata: { ...metadata, description }, }); } async handleWebhook( sig: string, body: Buffer ): Promise { return stripe.webhooks.constructEvent(body, sig, config.stripe.webhookSecret); } async getInvoiceHistory(customerId: string): Promise> { return await stripe.invoices.list({ customer: customerId, limit: 100, }); } }