import { Request, Response, NextFunction } from "express" import { prisma, createCheckoutSession, getSubscription as fetchSubscription, cancelSubscription as cancelStripeSubscription, } from "@codeflash-ai/common" import * as Sentry from "@sentry/node" import { logger } from "../utils/logger.js" import { missingRequiredFields, subscriptionNotFound } from "../exceptions/index.js" // Dependencies interface for easier testing export interface SubscriptionDependencies { prisma: { subscriptions: { findUnique: (params: any) => Promise } } createCheckoutSession: typeof createCheckoutSession fetchSubscription: typeof fetchSubscription // Add this cancelStripeSubscription: typeof cancelStripeSubscription Sentry: { captureException: (error: any) => void } } // Default dependencies let dependencies: SubscriptionDependencies = { prisma, createCheckoutSession, fetchSubscription, cancelStripeSubscription, Sentry, } // For testing - allow dependency injection export function setSubscriptionDependencies(deps: Partial) { dependencies = { ...dependencies, ...deps } } export function resetSubscriptionDependencies() { dependencies = { prisma, createCheckoutSession, fetchSubscription, cancelStripeSubscription, Sentry, } } // Get a user's subscription details export async function getSubscription(req: Request, res: Response, next: NextFunction) { const userId = req.query.userId as string if (!userId) { next(missingRequiredFields("userId")) return } try { // Get subscription with usage data (includes lazy reset) const subscription = await dependencies.fetchSubscription(userId) if (!subscription) { next(subscriptionNotFound(userId)) return } return res.json({ plan: subscription.plan_type, status: subscription.subscription_status, usageCount: subscription.optimizations_used, usageLimit: subscription.optimizations_limit, renewalDate: subscription.current_period_end, totalLifetimeOptimizations: subscription.total_lifetime_optimizations, }) } catch (error) { logger.errorWithSentry("Error getting subscription", req, {}, error as Error) dependencies.Sentry.captureException(error) next(error) // Pass errors to the error handler } } // Create a checkout session for upgrading subscription export async function createCheckout(req: Request, res: Response, next: NextFunction) { const { userId, priceId, successUrl, cancelUrl, period } = req.body if (!userId || !priceId) { next(missingRequiredFields("userId, priceId")) return } try { // Use the common function with options object const checkoutUrl = await dependencies.createCheckoutSession(userId, priceId, { successUrl: successUrl || `${process.env.WEBAPP_URL}/app/billing?success=true`, cancelUrl: cancelUrl || `${process.env.WEBAPP_URL}/app/billing?canceled=true`, period: period || (priceId === process.env.STRIPE_PRO_PRICE_YEARLY_ID ? "yearly" : "monthly"), }) return res.json({ url: checkoutUrl }) } catch (error) { logger.errorWithSentry( "Error creating checkout session", req, { successUrl, cancelUrl, period }, error as Error, ) dependencies.Sentry.captureException(error) next(error) // Pass errors to the error handler } } // Cancel a subscription export async function cancelSubscription(req: Request, res: Response, next: NextFunction) { const { userId } = req.body if (!userId) { next(missingRequiredFields("userId")) return } try { // Use the common function for cancellation await dependencies.cancelStripeSubscription(userId) return res.json({ status: "canceled" }) } catch (error) { logger.errorWithSentry("Error canceling subscription", req, {}, error as Error) dependencies.Sentry.captureException(error) next(error) // Pass errors to the error handler } }