2
0

🚸 (billing) Use Stripe checkout page for new subscription with existing customer

This commit is contained in:
Baptiste Arnaud
2023-05-05 14:20:02 -04:00
parent e15e27f0b4
commit b9f94cdf19
3 changed files with 95 additions and 44 deletions

View File

@@ -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
}

View File

@@ -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({

View File

@@ -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)
}