import { stripe, SubscriptionTier, tierConfig } from '../config/billing.config'; import { z } from 'zod'; // Subscription service export class SubscriptionService { /** * Create a new subscription for a customer */ async createSubscription( customerId: string, tier: SubscriptionTier, metadata?: Record ): Promise { const priceId = tierConfig[tier].priceId; const subscription = await stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], metadata: metadata, proration_behavior: 'create_prorations', }); return subscription; } /** * Update a customer's subscription tier */ async updateSubscriptionTier( subscriptionId: string, newTier: SubscriptionTier ): Promise { const newPriceId = tierConfig[newTier].priceId; const subscription = await stripe.subscriptions.update(subscriptionId, { items: [ { price: newPriceId, quantity: 1, }, ], proration_behavior: 'create_prorations', }); return subscription; } /** * Cancel a subscription */ async cancelSubscription( subscriptionId: string, atPeriodEnd: boolean = true ): Promise { const subscription = await stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: atPeriodEnd, }); return subscription; } /** * Get subscription by ID */ async getSubscription(subscriptionId: string): Promise { try { const subscription = await stripe.subscriptions.retrieve(subscriptionId); return subscription; } catch (error) { if (error instanceof Stripe.errors.StripeInvalidRequestError) { return null; } throw error; } } /** * Get customer's current subscription */ async getCustomerSubscription(customerId: string): Promise { const subscriptions = await stripe.subscriptions.list({ customer: customerId, status: 'active', limit: 1, }); return subscriptions.data[0] || null; } } // Customer service export class CustomerService { /** * Create a new Stripe customer */ async createCustomer( email: string, name?: string, metadata?: Record ): Promise { const customer = await stripe.customers.create({ email, name, metadata, }); return customer; } /** * Get or create customer by email */ async getOrCreateCustomer( email: string, name?: string ): Promise { const existingCustomers = await stripe.customers.list({ email, limit: 1, }); if (existingCustomers.data.length > 0) { return existingCustomers.data[0]; } return this.createCustomer(email, name); } /** * Create a billing portal session */ async createBillingPortalSession( customerId: string, returnUrl: string ): Promise { const session = await stripe.billingPortal.sessions.create({ customer: customerId, return_url: returnUrl, }); return session; } /** * Get customer by ID */ async getCustomer(customerId: string): Promise { try { const customer = await stripe.customers.retrieve(customerId); return customer as Stripe.Customer; } catch (error) { if (error instanceof Stripe.errors.StripeInvalidRequestError) { return null; } throw error; } } } // Webhook service export class WebhookService { /** * Construct webhook event from raw body */ constructEvent( rawBody: Buffer | string, signature: string ): Stripe.Event { return stripe.webhooks.constructEvent( rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET! ); } /** * Handle webhook event */ async handleWebhook(event: Stripe.Event): Promise { switch (event.type) { case 'customer.subscription.created': case 'customer.subscription.updated': await this.handleSubscriptionChange(event.data.object); break; case 'customer.subscription.deleted': await this.handleSubscriptionDeleted(event.data.object); break; case 'invoice.payment_succeeded': await this.handlePaymentSucceeded(event.data.object); break; case 'invoice.payment_failed': await this.handlePaymentFailed(event.data.object); break; default: console.log(`Unhandled event type: ${event.type}`); } } private async handleSubscriptionChange(subscription: Stripe.Subscription) { console.log(`Subscription ${subscription.id} changed to ${subscription.status}`); // TODO: Update local database } private async handleSubscriptionDeleted(subscription: Stripe.Subscription) { console.log(`Subscription ${subscription.id} deleted`); // TODO: Update local database } private async handlePaymentSucceeded(invoice: Stripe.Invoice) { console.log(`Payment succeeded for invoice ${invoice.id}`); // TODO: Update usage tracking } private async handlePaymentFailed(invoice: Stripe.Invoice) { console.log(`Payment failed for invoice ${invoice.id}`); // TODO: Send notification to customer } } // Export instances export const subscriptionService = new SubscriptionService(); export const customerService = new CustomerService(); export const webhookService = new WebhookService();