2023-03-15 08:35:16 +01:00
|
|
|
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
2023-02-17 16:19:39 +01:00
|
|
|
import prisma from '@/lib/prisma'
|
2023-03-15 11:51:30 +01:00
|
|
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
2023-02-17 16:19:39 +01:00
|
|
|
import { TRPCError } from '@trpc/server'
|
2023-03-15 08:35:16 +01:00
|
|
|
import { Plan, WorkspaceRole } from '@typebot.io/prisma'
|
|
|
|
|
import { workspaceSchema } from '@typebot.io/schemas'
|
2023-02-17 16:19:39 +01:00
|
|
|
import Stripe from 'stripe'
|
2023-03-15 08:35:16 +01:00
|
|
|
import { isDefined } from '@typebot.io/lib'
|
2023-02-17 16:19:39 +01:00
|
|
|
import { z } from 'zod'
|
2023-04-13 11:39:10 +02:00
|
|
|
import {
|
|
|
|
|
getChatsLimit,
|
|
|
|
|
getStorageLimit,
|
|
|
|
|
priceIds,
|
|
|
|
|
} from '@typebot.io/lib/pricing'
|
|
|
|
|
import { chatPriceIds, storagePriceIds } from './getSubscription'
|
2023-02-17 16:19:39 +01:00
|
|
|
|
|
|
|
|
export const updateSubscription = authenticatedProcedure
|
|
|
|
|
.meta({
|
|
|
|
|
openapi: {
|
|
|
|
|
method: 'PATCH',
|
|
|
|
|
path: '/billing/subscription',
|
|
|
|
|
protect: true,
|
|
|
|
|
summary: 'Update subscription',
|
|
|
|
|
tags: ['Billing'],
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
workspaceId: z.string(),
|
|
|
|
|
plan: z.enum([Plan.STARTER, Plan.PRO]),
|
|
|
|
|
additionalChats: z.number(),
|
|
|
|
|
additionalStorage: z.number(),
|
|
|
|
|
currency: z.enum(['usd', 'eur']),
|
2023-04-13 11:39:10 +02:00
|
|
|
isYearly: z.boolean(),
|
2023-02-17 16:19:39 +01:00
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.output(
|
|
|
|
|
z.object({
|
|
|
|
|
workspace: workspaceSchema,
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(
|
|
|
|
|
async ({
|
|
|
|
|
input: {
|
|
|
|
|
workspaceId,
|
|
|
|
|
plan,
|
|
|
|
|
additionalChats,
|
|
|
|
|
additionalStorage,
|
|
|
|
|
currency,
|
2023-04-13 11:39:10 +02:00
|
|
|
isYearly,
|
2023-02-17 16:19:39 +01:00
|
|
|
},
|
|
|
|
|
ctx: { user },
|
|
|
|
|
}) => {
|
2023-04-13 11:39:10 +02:00
|
|
|
if (!process.env.STRIPE_SECRET_KEY)
|
2023-02-17 16:19:39 +01:00
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'INTERNAL_SERVER_ERROR',
|
|
|
|
|
message: 'Stripe environment variables are missing',
|
|
|
|
|
})
|
|
|
|
|
const workspace = await prisma.workspace.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
id: workspaceId,
|
|
|
|
|
members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } },
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if (!workspace?.stripeId)
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'NOT_FOUND',
|
|
|
|
|
message: 'Workspace not found',
|
|
|
|
|
})
|
|
|
|
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
|
|
|
|
apiVersion: '2022-11-15',
|
|
|
|
|
})
|
|
|
|
|
const { data } = await stripe.subscriptions.list({
|
|
|
|
|
customer: workspace.stripeId,
|
|
|
|
|
})
|
|
|
|
|
const subscription = data[0] as Stripe.Subscription | undefined
|
2023-04-13 11:39:10 +02:00
|
|
|
const currentPlanItemId = subscription?.items.data.find((item) =>
|
|
|
|
|
[
|
|
|
|
|
process.env.STRIPE_STARTER_PRODUCT_ID,
|
|
|
|
|
process.env.STRIPE_PRO_PRODUCT_ID,
|
|
|
|
|
].includes(item.price.product.toString())
|
2023-02-17 16:19:39 +01:00
|
|
|
)?.id
|
|
|
|
|
const currentAdditionalChatsItemId = subscription?.items.data.find(
|
2023-04-13 11:39:10 +02:00
|
|
|
(item) => chatPriceIds.includes(item.price.id)
|
2023-02-17 16:19:39 +01:00
|
|
|
)?.id
|
|
|
|
|
const currentAdditionalStorageItemId = subscription?.items.data.find(
|
2023-04-13 11:39:10 +02:00
|
|
|
(item) => storagePriceIds.includes(item.price.id)
|
2023-02-17 16:19:39 +01:00
|
|
|
)?.id
|
2023-04-13 11:39:10 +02:00
|
|
|
const frequency = isYearly ? 'yearly' : 'monthly'
|
|
|
|
|
|
2023-02-17 16:19:39 +01:00
|
|
|
const items = [
|
|
|
|
|
{
|
2023-04-13 11:39:10 +02:00
|
|
|
id: currentPlanItemId,
|
|
|
|
|
price: priceIds[plan].base[frequency],
|
2023-02-17 16:19:39 +01:00
|
|
|
quantity: 1,
|
|
|
|
|
},
|
|
|
|
|
additionalChats === 0 && !currentAdditionalChatsItemId
|
|
|
|
|
? undefined
|
|
|
|
|
: {
|
|
|
|
|
id: currentAdditionalChatsItemId,
|
2023-04-13 11:39:10 +02:00
|
|
|
price: priceIds[plan].chats[frequency],
|
|
|
|
|
quantity: getChatsLimit({
|
|
|
|
|
plan,
|
|
|
|
|
additionalChatsIndex: additionalChats,
|
|
|
|
|
customChatsLimit: null,
|
|
|
|
|
}),
|
2023-02-17 16:19:39 +01:00
|
|
|
deleted: subscription ? additionalChats === 0 : undefined,
|
|
|
|
|
},
|
|
|
|
|
additionalStorage === 0 && !currentAdditionalStorageItemId
|
|
|
|
|
? undefined
|
|
|
|
|
: {
|
|
|
|
|
id: currentAdditionalStorageItemId,
|
2023-04-13 11:39:10 +02:00
|
|
|
price: priceIds[plan].storage[frequency],
|
|
|
|
|
quantity: getStorageLimit({
|
|
|
|
|
plan,
|
|
|
|
|
additionalStorageIndex: additionalStorage,
|
|
|
|
|
customStorageLimit: null,
|
|
|
|
|
}),
|
2023-02-17 16:19:39 +01:00
|
|
|
deleted: subscription ? additionalStorage === 0 : undefined,
|
|
|
|
|
},
|
|
|
|
|
].filter(isDefined)
|
|
|
|
|
|
|
|
|
|
if (subscription) {
|
|
|
|
|
await stripe.subscriptions.update(subscription.id, {
|
|
|
|
|
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,
|
|
|
|
|
currency,
|
|
|
|
|
default_payment_method: paymentMethods[0].id,
|
2023-04-13 11:39:10 +02:00
|
|
|
automatic_tax: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
2023-02-17 16:19:39 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedWorkspace = await prisma.workspace.update({
|
|
|
|
|
where: { id: workspaceId },
|
|
|
|
|
data: {
|
|
|
|
|
plan,
|
|
|
|
|
additionalChatsIndex: additionalChats,
|
|
|
|
|
additionalStorageIndex: additionalStorage,
|
|
|
|
|
chatsLimitFirstEmailSentAt: null,
|
|
|
|
|
chatsLimitSecondEmailSentAt: null,
|
|
|
|
|
storageLimitFirstEmailSentAt: null,
|
|
|
|
|
storageLimitSecondEmailSentAt: null,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2023-03-14 14:18:05 +01:00
|
|
|
await sendTelemetryEvents([
|
|
|
|
|
{
|
|
|
|
|
name: 'Subscription updated',
|
|
|
|
|
workspaceId,
|
|
|
|
|
userId: user.id,
|
|
|
|
|
data: {
|
|
|
|
|
plan,
|
|
|
|
|
additionalChatsIndex: additionalChats,
|
|
|
|
|
additionalStorageIndex: additionalStorage,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
|
2023-02-17 16:19:39 +01:00
|
|
|
return { workspace: updatedWorkspace }
|
|
|
|
|
}
|
|
|
|
|
)
|