♻️ (billing) Refactor billing server code to trpc
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Plan, WorkspaceRole } from 'db'
|
||||
import Stripe from 'stripe'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const cancelSubscription = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'DELETE',
|
||||
path: '/billing/subscription',
|
||||
protect: true,
|
||||
summary: 'Cancel current subscription',
|
||||
tags: ['Billing'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
message: z.literal('success'),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
if (
|
||||
!process.env.STRIPE_SECRET_KEY ||
|
||||
!process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID ||
|
||||
!process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)
|
||||
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 currentSubscriptionId = (
|
||||
await stripe.subscriptions.list({
|
||||
customer: workspace.stripeId,
|
||||
})
|
||||
).data.shift()?.id
|
||||
if (currentSubscriptionId)
|
||||
await stripe.subscriptions.del(currentSubscriptionId)
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspace.id },
|
||||
data: {
|
||||
plan: Plan.FREE,
|
||||
additionalChatsIndex: 0,
|
||||
additionalStorageIndex: 0,
|
||||
},
|
||||
})
|
||||
|
||||
return { message: 'success' }
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Plan, WorkspaceRole } from 'db'
|
||||
import Stripe from 'stripe'
|
||||
import { z } from 'zod'
|
||||
import { parseSubscriptionItems } from '../utils/parseSubscriptionItems'
|
||||
|
||||
export const createCheckoutSession = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/billing/subscription/checkout',
|
||||
protect: true,
|
||||
summary: 'Create checkout session to create a new subscription',
|
||||
tags: ['Billing'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
workspaceId: z.string(),
|
||||
prefilledEmail: z.string().optional(),
|
||||
currency: z.enum(['usd', 'eur']),
|
||||
plan: z.enum([Plan.STARTER, Plan.PRO]),
|
||||
returnUrl: z.string(),
|
||||
additionalChats: z.number(),
|
||||
additionalStorage: z.number(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
checkoutUrl: z.string(),
|
||||
})
|
||||
)
|
||||
.mutation(
|
||||
async ({
|
||||
input: {
|
||||
workspaceId,
|
||||
prefilledEmail,
|
||||
currency,
|
||||
plan,
|
||||
returnUrl,
|
||||
additionalChats,
|
||||
additionalStorage,
|
||||
},
|
||||
ctx: { user },
|
||||
}) => {
|
||||
if (
|
||||
!process.env.STRIPE_SECRET_KEY ||
|
||||
!process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID ||
|
||||
!process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)
|
||||
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)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Workspace not found',
|
||||
})
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2022-11-15',
|
||||
})
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
success_url: `${returnUrl}?stripe=${plan}&success=true`,
|
||||
cancel_url: `${returnUrl}?stripe=cancel`,
|
||||
allow_promotion_codes: true,
|
||||
customer_email: prefilledEmail,
|
||||
mode: 'subscription',
|
||||
metadata: { workspaceId, plan, additionalChats, additionalStorage },
|
||||
currency,
|
||||
automatic_tax: { enabled: true },
|
||||
line_items: parseSubscriptionItems(
|
||||
plan,
|
||||
additionalChats,
|
||||
additionalStorage
|
||||
),
|
||||
})
|
||||
|
||||
if (!session.url)
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Stripe checkout session creation failed',
|
||||
})
|
||||
|
||||
return {
|
||||
checkoutUrl: session.url,
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import Stripe from 'stripe'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const getBillingPortalUrl = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/billing/subscription/portal',
|
||||
protect: true,
|
||||
summary: 'Get Stripe billing portal URL',
|
||||
tags: ['Billing'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
billingPortalUrl: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
if (!process.env.STRIPE_SECRET_KEY)
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'STRIPE_SECRET_KEY var is missing',
|
||||
})
|
||||
const workspace = await prisma.workspace.findFirst({
|
||||
where: {
|
||||
id: workspaceId,
|
||||
members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } },
|
||||
},
|
||||
select: {
|
||||
stripeId: true,
|
||||
},
|
||||
})
|
||||
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 portalSession = await stripe.billingPortal.sessions.create({
|
||||
customer: workspace.stripeId,
|
||||
return_url: `${process.env.NEXTAUTH_URL}/typebots`,
|
||||
})
|
||||
return {
|
||||
billingPortalUrl: portalSession.url,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import Stripe from 'stripe'
|
||||
import { z } from 'zod'
|
||||
import { subscriptionSchema } from 'models/features/billing/subscription'
|
||||
|
||||
export const getSubscription = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/billing/subscription',
|
||||
protect: true,
|
||||
summary: 'List invoices',
|
||||
tags: ['Billing'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
subscription: subscriptionSchema,
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
if (
|
||||
!process.env.STRIPE_SECRET_KEY ||
|
||||
!process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID ||
|
||||
!process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)
|
||||
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 subscriptions = await stripe.subscriptions.list({
|
||||
customer: workspace.stripeId,
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
const subscription = subscriptions?.data.shift()
|
||||
|
||||
if (!subscription)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Subscription not found',
|
||||
})
|
||||
|
||||
return {
|
||||
subscription: {
|
||||
additionalChatsIndex:
|
||||
subscription?.items.data.find(
|
||||
(item) =>
|
||||
item.price.id === process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID
|
||||
)?.quantity ?? 0,
|
||||
additionalStorageIndex:
|
||||
subscription.items.data.find(
|
||||
(item) =>
|
||||
item.price.id === process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)?.quantity ?? 0,
|
||||
currency: subscription.currency as 'usd' | 'eur',
|
||||
},
|
||||
}
|
||||
})
|
||||
94
apps/builder/src/features/billing/api/procedures/getUsage.ts
Normal file
94
apps/builder/src/features/billing/api/procedures/getUsage.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const getUsage = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/billing/usage',
|
||||
protect: true,
|
||||
summary: 'Get current plan usage',
|
||||
tags: ['Billing'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({ totalChatsUsed: z.number(), totalStorageUsed: z.number() })
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
if (
|
||||
!process.env.STRIPE_SECRET_KEY ||
|
||||
!process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID ||
|
||||
!process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)
|
||||
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 } },
|
||||
},
|
||||
})
|
||||
if (!workspace)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Workspace not found',
|
||||
})
|
||||
|
||||
const now = new Date()
|
||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const firstDayOfNextMonth = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth() + 1,
|
||||
1
|
||||
)
|
||||
const [
|
||||
totalChatsUsed,
|
||||
{
|
||||
_sum: { storageUsed: totalStorageUsed },
|
||||
},
|
||||
] = await prisma.$transaction(async (tx) => {
|
||||
const typebots = await tx.typebot.findMany({
|
||||
where: {
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return Promise.all([
|
||||
prisma.result.count({
|
||||
where: {
|
||||
typebotId: { in: typebots.map((typebot) => typebot.id) },
|
||||
hasStarted: true,
|
||||
createdAt: {
|
||||
gte: firstDayOfMonth,
|
||||
lt: firstDayOfNextMonth,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.answer.aggregate({
|
||||
where: {
|
||||
storageUsed: { gt: 0 },
|
||||
result: {
|
||||
typebotId: { in: typebots.map((typebot) => typebot.id) },
|
||||
},
|
||||
},
|
||||
_sum: { storageUsed: true },
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
return {
|
||||
totalChatsUsed,
|
||||
totalStorageUsed: totalStorageUsed ?? 0,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import Stripe from 'stripe'
|
||||
import { isDefined } from 'utils'
|
||||
import { z } from 'zod'
|
||||
import { invoiceSchema } from 'models/features/billing/invoice'
|
||||
|
||||
export const listInvoices = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/billing/invoices',
|
||||
protect: true,
|
||||
summary: 'List invoices',
|
||||
tags: ['Billing'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
invoices: z.array(invoiceSchema),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||
if (!process.env.STRIPE_SECRET_KEY)
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'STRIPE_SECRET_KEY var is 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 invoices = await stripe.invoices.list({
|
||||
customer: workspace.stripeId,
|
||||
})
|
||||
return {
|
||||
invoices: invoices.data
|
||||
.filter(
|
||||
(invoice) => isDefined(invoice.invoice_pdf) && isDefined(invoice.id)
|
||||
)
|
||||
.map((i) => ({
|
||||
id: i.number as string,
|
||||
url: i.invoice_pdf as string,
|
||||
amount: i.subtotal,
|
||||
currency: i.currency,
|
||||
date: i.status_transitions.paid_at,
|
||||
})),
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,146 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/utils/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { Plan, WorkspaceRole } from 'db'
|
||||
import { workspaceSchema } from 'models'
|
||||
import Stripe from 'stripe'
|
||||
import { isDefined } from 'utils'
|
||||
import { z } from 'zod'
|
||||
|
||||
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']),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
workspace: workspaceSchema,
|
||||
})
|
||||
)
|
||||
.mutation(
|
||||
async ({
|
||||
input: {
|
||||
workspaceId,
|
||||
plan,
|
||||
additionalChats,
|
||||
additionalStorage,
|
||||
currency,
|
||||
},
|
||||
ctx: { user },
|
||||
}) => {
|
||||
if (
|
||||
!process.env.STRIPE_SECRET_KEY ||
|
||||
!process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID ||
|
||||
!process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)
|
||||
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
|
||||
const currentStarterPlanItemId = subscription?.items.data.find(
|
||||
(item) => item.price.id === process.env.STRIPE_STARTER_PRICE_ID
|
||||
)?.id
|
||||
const currentProPlanItemId = subscription?.items.data.find(
|
||||
(item) => item.price.id === process.env.STRIPE_PRO_PRICE_ID
|
||||
)?.id
|
||||
const currentAdditionalChatsItemId = subscription?.items.data.find(
|
||||
(item) => item.price.id === process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID
|
||||
)?.id
|
||||
const currentAdditionalStorageItemId = subscription?.items.data.find(
|
||||
(item) =>
|
||||
item.price.id === process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID
|
||||
)?.id
|
||||
const items = [
|
||||
{
|
||||
id: currentStarterPlanItemId ?? currentProPlanItemId,
|
||||
price:
|
||||
plan === Plan.STARTER
|
||||
? process.env.STRIPE_STARTER_PRICE_ID
|
||||
: process.env.STRIPE_PRO_PRICE_ID,
|
||||
quantity: 1,
|
||||
},
|
||||
additionalChats === 0 && !currentAdditionalChatsItemId
|
||||
? undefined
|
||||
: {
|
||||
id: currentAdditionalChatsItemId,
|
||||
price: process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID,
|
||||
quantity: additionalChats,
|
||||
deleted: subscription ? additionalChats === 0 : undefined,
|
||||
},
|
||||
additionalStorage === 0 && !currentAdditionalStorageItemId
|
||||
? undefined
|
||||
: {
|
||||
id: currentAdditionalStorageItemId,
|
||||
price: process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID,
|
||||
quantity: additionalStorage,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
const updatedWorkspace = await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: {
|
||||
plan,
|
||||
additionalChatsIndex: additionalChats,
|
||||
additionalStorageIndex: additionalStorage,
|
||||
chatsLimitFirstEmailSentAt: null,
|
||||
chatsLimitSecondEmailSentAt: null,
|
||||
storageLimitFirstEmailSentAt: null,
|
||||
storageLimitSecondEmailSentAt: null,
|
||||
},
|
||||
})
|
||||
|
||||
return { workspace: updatedWorkspace }
|
||||
}
|
||||
)
|
||||
18
apps/builder/src/features/billing/api/router.ts
Normal file
18
apps/builder/src/features/billing/api/router.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { router } from '@/utils/server/trpc'
|
||||
import { getBillingPortalUrl } from './procedures/getBillingPortalUrl'
|
||||
import { listInvoices } from './procedures/listInvoices'
|
||||
import { cancelSubscription } from './procedures/cancelSubscription'
|
||||
import { createCheckoutSession } from './procedures/createCheckoutSession'
|
||||
import { updateSubscription } from './procedures/updateSubscription'
|
||||
import { getSubscription } from './procedures/getSubscription'
|
||||
import { getUsage } from './procedures/getUsage'
|
||||
|
||||
export const billingRouter = router({
|
||||
getBillingPortalUrl,
|
||||
listInvoices,
|
||||
cancelSubscription,
|
||||
createCheckoutSession,
|
||||
updateSubscription,
|
||||
getSubscription,
|
||||
getUsage,
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Plan } from 'db'
|
||||
|
||||
export const parseSubscriptionItems = (
|
||||
plan: Plan,
|
||||
additionalChats: number,
|
||||
additionalStorage: number
|
||||
) =>
|
||||
[
|
||||
{
|
||||
price:
|
||||
plan === Plan.STARTER
|
||||
? process.env.STRIPE_STARTER_PRICE_ID
|
||||
: process.env.STRIPE_PRO_PRICE_ID,
|
||||
quantity: 1,
|
||||
},
|
||||
]
|
||||
.concat(
|
||||
additionalChats > 0
|
||||
? [
|
||||
{
|
||||
price: process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID,
|
||||
quantity: additionalChats,
|
||||
},
|
||||
]
|
||||
: []
|
||||
)
|
||||
.concat(
|
||||
additionalStorage > 0
|
||||
? [
|
||||
{
|
||||
price: process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID,
|
||||
quantity: additionalStorage,
|
||||
},
|
||||
]
|
||||
: []
|
||||
)
|
||||
Reference in New Issue
Block a user