Skip to main content
Glama
stripe.controller.ts8.65 kB
import type { Locale } from '@intlayer/types'; import type { ResponseWithSession } from '@middlewares/sessionAuth.middleware'; import * as emailService from '@services/email.service'; import * as subscriptionService from '@services/subscription.service'; import { type AppError, ErrorHandler } from '@utils/errors'; import { retrievePlanInformation } from '@utils/plan'; import { formatResponse, type ResponseData } from '@utils/responseData'; import type { Request } from 'express'; import { t } from 'express-intlayer'; import { Stripe } from 'stripe'; import type { Organization } from '@/types/organization.types'; export type GetPricingBody = { priceIds: string[]; promoCode?: string; }; export type GetPricingResult = ResponseData<subscriptionService.PricingResult>; /** * Simulate pricing for a given set of prices and a promotion code. * * @param req - The request object containing the price IDs and promotion code. * @param res - The response object to send the simulated pricing result. */ export const getPricing = async ( req: Request<undefined, undefined, GetPricingBody>, res: ResponseWithSession<GetPricingResult> ) => { const { priceIds, promoCode } = req.body; const pricingResult = await subscriptionService.getPricing( priceIds, promoCode ); const formattedPricingResult = formatResponse<subscriptionService.PricingResult>({ data: pricingResult, }); res.status(200).json(formattedPricingResult); }; export type GetCheckoutSessionBody = { priceId: string; promoCode?: string; }; export type GetCheckoutSessionResult = ResponseData<{ subscription: Stripe.Response<Stripe.Subscription>; clientSecret: string; }>; /** * Handles subscription creation or update with Stripe and returns a ClientSecret. * @param req - Express request object. * @param res - Express response object. */ export const getSubscription = async ( req: Request<undefined, undefined, GetCheckoutSessionBody>, res: ResponseWithSession<GetCheckoutSessionResult> ): Promise<void> => { try { const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); // Extract organization and user from response locals (set by authentication middleware) const { organization, user } = res.locals; // Get the price ID (Stripe Price ID) from the request body const { priceId, promoCode } = req.body; // Validate that the organization exists if (!organization) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND'); return; } // Validate that the user exists if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND'); return; } const { period, type } = retrievePlanInformation(priceId); if ( organization.plan?.subscriptionId && organization.plan?.type === type && organization.plan?.period === period && organization.plan?.status === 'active' ) { ErrorHandler.handleGenericErrorResponse(res, 'ALREADY_SUBSCRIBED', { organizationId: organization.id, }); return; } // Attempt to retrieve the Stripe customer ID from the organization's plan let customerId = organization.plan?.customerId; if (!customerId) { // If no customer ID exists, create a new Stripe customer for the organization const customer = await stripe.customers.create({ metadata: { organizationId: String(organization.id), userId: String(user.id), // Include the locale for potential localization locale: (res.locals as unknown as { locale: Locale }).locale, }, }); customerId = customer.id; } const promoCodeId = promoCode ? await subscriptionService.getCouponId(promoCode) : null; const discounts: Stripe.SubscriptionCreateParams.Discount[] = promoCodeId ? [{ coupon: promoCodeId }] : []; // If no subscription exists, create a new one const subscription = await stripe.subscriptions.create({ customer: customerId, // Associate the subscription with the customer items: [{ price: priceId }], // Set the price ID for the subscription expand: ['latest_invoice.confirmation_secret'], payment_settings: { payment_method_types: ['card'], // Specify payment method types }, payment_behavior: 'default_incomplete', // Create the subscription in an incomplete state until payment is confirmed discounts, }); // Handle subscription creation failure if (!subscription) { ErrorHandler.handleGenericErrorResponse( res, 'SUBSCRIPTION_CREATION_FAILED', { user, organization, priceId, } ); return; } const clientSecret = ( subscription.latest_invoice as Stripe.Invoice & { confirmation_secret?: { client_secret: string }; } )?.confirmation_secret?.client_secret; // Handle subscription creation failure if (!clientSecret) { ErrorHandler.handleGenericErrorResponse( res, 'SUBSCRIPTION_CREATION_FAILED', { user, organization, priceId, } ); return; } // Prepare the response data with subscription details const responseData = formatResponse<GetCheckoutSessionResult['data']>({ data: { subscription, clientSecret }, }); // Send the response back to the client res.json(responseData); return; } catch (error) { // Handle any errors that occur during the process ErrorHandler.handleAppErrorResponse(res, error as AppError); return; } }; type CancelSubscriptionData = Organization['plan']; type CancelSubscriptionResult = ResponseData<CancelSubscriptionData>; /** * Cancels a subscription for an organization. * @param _req - Express request object. * @param res - Express response object. */ export const cancelSubscription = async ( _req: Request, res: ResponseWithSession<CancelSubscriptionResult> ): Promise<void> => { const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); try { // Extract the organization and user from the response locals // These are typically set by authentication middleware earlier in the request pipeline const { organization, user } = res.locals; // Validate that the organization exists if (!organization) { ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND'); return; } // Validate that the user exists if (!user) { ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND'); return; } // Check if the organization has an active subscription to cancel if (!organization.plan?.subscriptionId) { ErrorHandler.handleGenericErrorResponse( res, 'ORGANIZATION_PLAN_NOT_FOUND' ); return; } // Cancel the subscription on Stripe immediately using the subscription ID await stripe.subscriptions.cancel(organization.plan.subscriptionId); // Update the organization's plan in the database to reflect the cancellation const plan = await subscriptionService.cancelSubscription( organization.plan.subscriptionId, String(organization.id) ); // If the plan could not be updated in the database, handle the error if (!plan) { ErrorHandler.handleGenericErrorResponse( res, 'ORGANIZATION_PLAN_NOT_FOUND' ); return; } // Prepare a formatted response with a success message and the updated plan data const formattedPlan = formatResponse<CancelSubscriptionData>({ message: t({ en: 'Subscription cancelled successfully', fr: 'Souscription annulée avec succès', es: 'Suscripción cancelada con éxito', }), description: t({ en: 'Your subscription has been cancelled successfully', fr: 'Votre souscription a été annulée avec succès', es: 'Su suscripción ha sido cancelada con éxito', }), data: plan!, }); // Send the response back to the client res.json(formattedPlan); await emailService.sendEmail({ type: 'subscriptionPaymentCancellation', to: user.email, email: user.email, cancellationDate: new Date().toLocaleDateString(), reactivateLink: `${process.env.CLIENT_URL}/pricing`, username: user.name, organizationName: organization.name, planName: plan.type, }); } catch (error) { // Handle any errors that occur during the cancellation process ErrorHandler.handleAppErrorResponse(res, error as AppError); } };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server