diff --git a/apps/builder/src/features/billing/api/createCheckoutSession.ts b/apps/builder/src/features/billing/api/createCheckoutSession.ts index b847f540dcf..a1b38c08de8 100644 --- a/apps/builder/src/features/billing/api/createCheckoutSession.ts +++ b/apps/builder/src/features/billing/api/createCheckoutSession.ts @@ -94,42 +94,82 @@ export const createCheckoutSession = authenticatedProcedure : undefined, }) - const session = await stripe.checkout.sessions.create({ - success_url: `${returnUrl}?stripe=${plan}&success=true`, - cancel_url: `${returnUrl}?stripe=cancel`, - allow_promotion_codes: true, - customer: customer.id, - customer_update: { - address: 'auto', - name: 'never', - }, - mode: 'subscription', - metadata: { - workspaceId, - plan, - additionalChats, - additionalStorage, - userId: user.id, - }, + const checkoutUrl = await createCheckoutSessionUrl(stripe)({ + customerId: customer.id, + userId: user.id, + workspaceId, currency, - billing_address_collection: 'required', - automatic_tax: { enabled: true }, - line_items: parseSubscriptionItems( - plan, - additionalChats, - additionalStorage, - isYearly - ), + plan, + returnUrl, + additionalChats, + additionalStorage, + isYearly, }) - if (!session.url) + if (!checkoutUrl) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Stripe checkout session creation failed', }) return { - checkoutUrl: session.url, + checkoutUrl, } } ) + +type Props = { + customerId: string + workspaceId: string + currency: 'usd' | 'eur' + plan: 'STARTER' | 'PRO' + returnUrl: string + additionalChats: number + additionalStorage: number + isYearly: boolean + userId: string +} + +export const createCheckoutSessionUrl = + (stripe: Stripe) => + async ({ + customerId, + userId, + workspaceId, + currency, + plan, + returnUrl, + additionalChats, + additionalStorage, + isYearly, + }: Props) => { + const session = await stripe.checkout.sessions.create({ + success_url: `${returnUrl}?stripe=${plan}&success=true`, + cancel_url: `${returnUrl}?stripe=cancel`, + allow_promotion_codes: true, + customer: customerId, + customer_update: { + address: 'auto', + name: 'never', + }, + mode: 'subscription', + metadata: { + workspaceId, + plan, + additionalChats, + additionalStorage, + userId, + }, + currency, + billing_address_collection: 'required', + automatic_tax: { enabled: true }, + line_items: parseSubscriptionItems( + plan, + additionalChats, + additionalStorage, + isYearly + ), + }) + + return session.url + } diff --git a/apps/builder/src/features/billing/api/updateSubscription.ts b/apps/builder/src/features/billing/api/updateSubscription.ts index 3d8c89e45e9..1ad4078a1cf 100644 --- a/apps/builder/src/features/billing/api/updateSubscription.ts +++ b/apps/builder/src/features/billing/api/updateSubscription.ts @@ -13,6 +13,7 @@ import { priceIds, } from '@typebot.io/lib/pricing' import { chatPriceIds, storagePriceIds } from './getSubscription' +import { createCheckoutSessionUrl } from './createCheckoutSession' export const updateSubscription = authenticatedProcedure .meta({ @@ -26,6 +27,7 @@ export const updateSubscription = authenticatedProcedure }) .input( z.object({ + returnUrl: z.string(), workspaceId: z.string(), plan: z.enum([Plan.STARTER, Plan.PRO]), additionalChats: z.number(), @@ -36,7 +38,8 @@ export const updateSubscription = authenticatedProcedure ) .output( z.object({ - workspace: workspaceSchema, + workspace: workspaceSchema.nullish(), + checkoutUrl: z.string().nullish(), }) ) .mutation( @@ -48,6 +51,7 @@ export const updateSubscription = authenticatedProcedure additionalStorage, currency, isYearly, + returnUrl, }, ctx: { user }, }) => { @@ -127,21 +131,19 @@ export const updateSubscription = authenticatedProcedure items, }) } else { - const { data: paymentMethods } = await stripe.paymentMethods.list({ - customer: workspace.stripeId, - }) - if (paymentMethods.length === 0) { - throw Error('No payment method found') - } - await stripe.subscriptions.create({ - customer: workspace.stripeId, - items, + const checkoutUrl = await createCheckoutSessionUrl(stripe)({ + customerId: workspace.stripeId, + userId: user.id, + workspaceId, currency, - default_payment_method: paymentMethods[0].id, - automatic_tax: { - enabled: true, - }, + plan, + returnUrl, + additionalChats, + additionalStorage, + isYearly, }) + + return { checkoutUrl } } const updatedWorkspace = await prisma.workspace.update({ diff --git a/apps/builder/src/features/billing/components/ChangePlanForm.tsx b/apps/builder/src/features/billing/components/ChangePlanForm.tsx index 5a7ed902f8e..93db69a2de9 100644 --- a/apps/builder/src/features/billing/components/ChangePlanForm.tsx +++ b/apps/builder/src/features/billing/components/ChangePlanForm.tsx @@ -47,11 +47,17 @@ export const ChangePlanForm = ({ workspace, onUpgradeSuccess }: Props) => { description: error.message, }) }, - onSuccess: ({ workspace: { plan } }) => { + onSuccess: ({ workspace, checkoutUrl }) => { + if (checkoutUrl) { + window.location.href = checkoutUrl + return + } onUpgradeSuccess() showToast({ status: 'success', - description: scopedT('updateSuccessToast.description', { plan }), + description: scopedT('updateSuccessToast.description', { + plan: workspace?.plan, + }), }) }, }) @@ -83,7 +89,10 @@ export const ChangePlanForm = ({ workspace, onUpgradeSuccess }: Props) => { isYearly, } as const if (workspace.stripeId) { - updateSubscription(newSubscription) + updateSubscription({ + ...newSubscription, + returnUrl: window.location.href, + }) } else { setPreCheckoutPlan(newSubscription) }