🚸 (billing) Use Stripe checkout page for new subscription with existing customer
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user