/** * @ai-summary Stripe API client wrapper * @ai-context Handles all Stripe API interactions with proper error handling */ import Stripe from 'stripe'; import { logger } from '../../../../core/logging/logger'; import { StripeCustomer, StripeSubscription, StripePaymentIntent, StripeWebhookEvent, } from './stripe.types'; export class StripeClient { private stripe: Stripe; constructor() { const apiKey = process.env.STRIPE_SECRET_KEY; if (!apiKey) { throw new Error('STRIPE_SECRET_KEY environment variable is required'); } this.stripe = new Stripe(apiKey, { apiVersion: '2025-12-15.clover', typescript: true, }); logger.info('Stripe client initialized'); } /** * Create a new Stripe customer */ async createCustomer(email: string, name?: string): Promise { try { logger.info('Creating Stripe customer', { email, name }); const customer = await this.stripe.customers.create({ email, name, metadata: { source: 'motovaultpro', }, }); logger.info('Stripe customer created', { customerId: customer.id }); return { id: customer.id, email: customer.email || email, name: customer.name || undefined, created: customer.created, metadata: customer.metadata, }; } catch (error: any) { logger.error('Failed to create Stripe customer', { email, error: error.message, code: error.code, }); throw error; } } /** * Create a new subscription for a customer */ async createSubscription( customerId: string, priceId: string, paymentMethodId?: string ): Promise { try { logger.info('Creating Stripe subscription', { customerId, priceId, paymentMethodId }); const subscriptionParams: Stripe.SubscriptionCreateParams = { customer: customerId, items: [{ price: priceId }], payment_behavior: 'default_incomplete', payment_settings: { save_default_payment_method: 'on_subscription', }, expand: ['latest_invoice.payment_intent'], }; if (paymentMethodId) { subscriptionParams.default_payment_method = paymentMethodId; } const subscription = await this.stripe.subscriptions.create(subscriptionParams); logger.info('Stripe subscription created', { subscriptionId: subscription.id }); return { id: subscription.id, customer: subscription.customer as string, status: subscription.status as StripeSubscription['status'], items: subscription.items, currentPeriodStart: (subscription as any).current_period_start || 0, currentPeriodEnd: (subscription as any).current_period_end || 0, cancelAtPeriodEnd: subscription.cancel_at_period_end, canceledAt: subscription.canceled_at || undefined, created: subscription.created, metadata: subscription.metadata, }; } catch (error: any) { logger.error('Failed to create Stripe subscription', { customerId, priceId, error: error.message, code: error.code, }); throw error; } } /** * Cancel a subscription */ async cancelSubscription( subscriptionId: string, cancelAtPeriodEnd: boolean = false ): Promise { try { logger.info('Canceling Stripe subscription', { subscriptionId, cancelAtPeriodEnd }); let subscription: Stripe.Subscription; if (cancelAtPeriodEnd) { // Cancel at period end (schedule cancellation) subscription = await this.stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true, }); logger.info('Stripe subscription scheduled for cancellation', { subscriptionId }); } else { // Cancel immediately subscription = await this.stripe.subscriptions.cancel(subscriptionId); logger.info('Stripe subscription canceled immediately', { subscriptionId }); } return { id: subscription.id, customer: subscription.customer as string, status: subscription.status as StripeSubscription['status'], items: subscription.items, currentPeriodStart: (subscription as any).current_period_start || 0, currentPeriodEnd: (subscription as any).current_period_end || 0, cancelAtPeriodEnd: subscription.cancel_at_period_end, canceledAt: subscription.canceled_at || undefined, created: subscription.created, metadata: subscription.metadata, }; } catch (error: any) { logger.error('Failed to cancel Stripe subscription', { subscriptionId, error: error.message, code: error.code, }); throw error; } } /** * Update the payment method for a customer */ async updatePaymentMethod(customerId: string, paymentMethodId: string): Promise { try { logger.info('Updating Stripe payment method', { customerId, paymentMethodId }); // Attach payment method to customer await this.stripe.paymentMethods.attach(paymentMethodId, { customer: customerId, }); // Set as default payment method await this.stripe.customers.update(customerId, { invoice_settings: { default_payment_method: paymentMethodId, }, }); logger.info('Stripe payment method updated', { customerId, paymentMethodId }); } catch (error: any) { logger.error('Failed to update Stripe payment method', { customerId, paymentMethodId, error: error.message, code: error.code, }); throw error; } } /** * Create a payment intent for one-time donations */ async createPaymentIntent(amount: number, currency: string = 'usd'): Promise { try { logger.info('Creating Stripe payment intent', { amount, currency }); const paymentIntent = await this.stripe.paymentIntents.create({ amount, currency, metadata: { source: 'motovaultpro', type: 'donation', }, }); logger.info('Stripe payment intent created', { paymentIntentId: paymentIntent.id }); return { id: paymentIntent.id, amount: paymentIntent.amount, currency: paymentIntent.currency, status: paymentIntent.status, customer: paymentIntent.customer as string | undefined, payment_method: paymentIntent.payment_method as string | undefined, created: paymentIntent.created, metadata: paymentIntent.metadata, }; } catch (error: any) { logger.error('Failed to create Stripe payment intent', { amount, currency, error: error.message, code: error.code, }); throw error; } } /** * Construct and verify a webhook event from Stripe */ constructWebhookEvent(payload: Buffer, signature: string): StripeWebhookEvent { try { const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; if (!webhookSecret) { throw new Error('STRIPE_WEBHOOK_SECRET environment variable is required'); } const event = this.stripe.webhooks.constructEvent( payload, signature, webhookSecret ); logger.info('Stripe webhook event verified', { eventId: event.id, type: event.type }); return { id: event.id, type: event.type, data: event.data, created: event.created, }; } catch (error: any) { logger.error('Failed to verify Stripe webhook event', { error: error.message, }); throw error; } } /** * Retrieve a subscription by ID */ async getSubscription(subscriptionId: string): Promise { try { logger.info('Retrieving Stripe subscription', { subscriptionId }); const subscription = await this.stripe.subscriptions.retrieve(subscriptionId); return { id: subscription.id, customer: subscription.customer as string, status: subscription.status as StripeSubscription['status'], items: subscription.items, currentPeriodStart: (subscription as any).current_period_start || 0, currentPeriodEnd: (subscription as any).current_period_end || 0, cancelAtPeriodEnd: subscription.cancel_at_period_end, canceledAt: subscription.canceled_at || undefined, created: subscription.created, metadata: subscription.metadata, }; } catch (error: any) { logger.error('Failed to retrieve Stripe subscription', { subscriptionId, error: error.message, code: error.code, }); throw error; } } /** * Retrieve a customer by ID */ async getCustomer(customerId: string): Promise { try { logger.info('Retrieving Stripe customer', { customerId }); const customer = await this.stripe.customers.retrieve(customerId); if (customer.deleted) { throw new Error('Customer has been deleted'); } return { id: customer.id, email: customer.email || '', name: customer.name || undefined, created: customer.created, metadata: customer.metadata, }; } catch (error: any) { logger.error('Failed to retrieve Stripe customer', { customerId, error: error.message, code: error.code, }); throw error; } } /** * List invoices for a customer */ async listInvoices(customerId: string): Promise { try { logger.info('Listing Stripe invoices', { customerId }); const invoices = await this.stripe.invoices.list({ customer: customerId, limit: 20, }); logger.info('Stripe invoices retrieved', { customerId, count: invoices.data.length }); return invoices.data; } catch (error: any) { logger.error('Failed to list Stripe invoices', { customerId, error: error.message, code: error.code, }); throw error; } } }