✨ (billing) Implement custom plan
This commit is contained in:
committed by
Baptiste Arnaud
parent
3f7dc79918
commit
385853ca3c
@@ -0,0 +1,30 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "Plan" ADD VALUE 'CUSTOM';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Workspace" ADD COLUMN "customChatsLimit" INTEGER,
|
||||
ADD COLUMN "customSeatsLimit" INTEGER,
|
||||
ADD COLUMN "customStorageLimit" INTEGER;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ClaimableCustomPlan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"claimedAt" TIMESTAMP(3),
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"price" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
"chatsLimit" INTEGER NOT NULL,
|
||||
"storageLimit" INTEGER NOT NULL,
|
||||
"seatsLimit" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "ClaimableCustomPlan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ClaimableCustomPlan_workspaceId_key" ON "ClaimableCustomPlan"("workspaceId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ClaimableCustomPlan" ADD CONSTRAINT "ClaimableCustomPlan_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -84,6 +84,10 @@ model Workspace {
|
||||
storageLimitFirstEmailSentAt DateTime?
|
||||
chatsLimitSecondEmailSentAt DateTime?
|
||||
storageLimitSecondEmailSentAt DateTime?
|
||||
claimableCustomPlan ClaimableCustomPlan?
|
||||
customChatsLimit Int?
|
||||
customStorageLimit Int?
|
||||
customSeatsLimit Int?
|
||||
}
|
||||
|
||||
model MemberInWorkspace {
|
||||
@@ -263,6 +267,21 @@ model Webhook {
|
||||
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model ClaimableCustomPlan {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
claimedAt DateTime?
|
||||
name String
|
||||
description String?
|
||||
price Int
|
||||
currency String
|
||||
workspaceId String @unique
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
chatsLimit Int
|
||||
storageLimit Int
|
||||
seatsLimit Int
|
||||
}
|
||||
|
||||
enum WorkspaceRole {
|
||||
ADMIN
|
||||
MEMBER
|
||||
@@ -280,6 +299,7 @@ enum Plan {
|
||||
PRO
|
||||
LIFETIME
|
||||
OFFERED
|
||||
CUSTOM
|
||||
}
|
||||
|
||||
enum CollaborationType {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Plan, PrismaClient, User, Workspace, WorkspaceRole } from 'db'
|
||||
import { Plan, Prisma, PrismaClient, User, Workspace, WorkspaceRole } from 'db'
|
||||
import cuid from 'cuid'
|
||||
import { Typebot, Webhook } from 'models'
|
||||
import { readFileSync } from 'fs'
|
||||
@@ -166,3 +166,13 @@ export const updateTypebot = async (
|
||||
data: partialTypebot,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateWorkspace = async (
|
||||
id: string,
|
||||
data: Prisma.WorkspaceUncheckedUpdateManyInput
|
||||
) => {
|
||||
await prisma.workspace.updateMany({
|
||||
where: { id: proWorkspaceId },
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,17 +13,16 @@ export const proWorkspaceId = 'proWorkspace'
|
||||
export const freeWorkspaceId = 'freeWorkspace'
|
||||
export const starterWorkspaceId = 'starterWorkspace'
|
||||
export const lifetimeWorkspaceId = 'lifetimeWorkspaceId'
|
||||
export const customWorkspaceId = 'customWorkspaceId'
|
||||
|
||||
const setupWorkspaces = async () => {
|
||||
await prisma.workspace.create({
|
||||
data: {
|
||||
id: freeWorkspaceId,
|
||||
name: 'Free workspace',
|
||||
plan: Plan.FREE,
|
||||
},
|
||||
})
|
||||
await prisma.workspace.createMany({
|
||||
data: [
|
||||
{
|
||||
id: freeWorkspaceId,
|
||||
name: 'Free workspace',
|
||||
plan: Plan.FREE,
|
||||
},
|
||||
{
|
||||
id: starterWorkspaceId,
|
||||
name: 'Starter workspace',
|
||||
@@ -40,6 +39,14 @@ const setupWorkspaces = async () => {
|
||||
name: 'Lifetime workspace',
|
||||
plan: Plan.LIFETIME,
|
||||
},
|
||||
{
|
||||
id: customWorkspaceId,
|
||||
name: 'Custom workspace',
|
||||
plan: Plan.CUSTOM,
|
||||
customChatsLimit: 100000,
|
||||
customStorageLimit: 50,
|
||||
customSeatsLimit: 20,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -99,6 +106,11 @@ export const setupUsers = async () => {
|
||||
userId,
|
||||
workspaceId: lifetimeWorkspaceId,
|
||||
},
|
||||
{
|
||||
role: WorkspaceRole.ADMIN,
|
||||
userId,
|
||||
workspaceId: customWorkspaceId,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export const storageLimit = {
|
||||
} as const
|
||||
|
||||
export const seatsLimit = {
|
||||
[Plan.FREE]: { totalIncluded: 0 },
|
||||
[Plan.FREE]: { totalIncluded: 1 },
|
||||
[Plan.STARTER]: {
|
||||
totalIncluded: 2,
|
||||
},
|
||||
@@ -62,7 +62,10 @@ export const seatsLimit = {
|
||||
export const getChatsLimit = ({
|
||||
plan,
|
||||
additionalChatsIndex,
|
||||
}: Pick<Workspace, 'additionalChatsIndex' | 'plan'>) => {
|
||||
customChatsLimit,
|
||||
}: Pick<Workspace, 'additionalChatsIndex' | 'plan' | 'customChatsLimit'>) => {
|
||||
if (plan === Plan.CUSTOM)
|
||||
return customChatsLimit ?? chatsLimit[Plan.FREE].totalIncluded
|
||||
const { totalIncluded } = chatsLimit[plan]
|
||||
const increaseStep =
|
||||
plan === Plan.STARTER || plan === Plan.PRO
|
||||
@@ -75,7 +78,13 @@ export const getChatsLimit = ({
|
||||
export const getStorageLimit = ({
|
||||
plan,
|
||||
additionalStorageIndex,
|
||||
}: Pick<Workspace, 'additionalStorageIndex' | 'plan'>) => {
|
||||
customStorageLimit,
|
||||
}: Pick<
|
||||
Workspace,
|
||||
'additionalStorageIndex' | 'plan' | 'customStorageLimit'
|
||||
>) => {
|
||||
if (plan === Plan.CUSTOM)
|
||||
return customStorageLimit ?? storageLimit[Plan.FREE].totalIncluded
|
||||
const { totalIncluded } = storageLimit[plan]
|
||||
const increaseStep =
|
||||
plan === Plan.STARTER || plan === Plan.PRO
|
||||
@@ -84,6 +93,15 @@ export const getStorageLimit = ({
|
||||
return totalIncluded + increaseStep.amount * additionalStorageIndex
|
||||
}
|
||||
|
||||
export const getSeatsLimit = ({
|
||||
plan,
|
||||
customSeatsLimit,
|
||||
}: Pick<Workspace, 'plan' | 'customSeatsLimit'>) => {
|
||||
if (plan === Plan.CUSTOM)
|
||||
return customSeatsLimit ?? seatsLimit[Plan.FREE].totalIncluded
|
||||
return seatsLimit[plan].totalIncluded
|
||||
}
|
||||
|
||||
export const computePrice = (
|
||||
plan: Plan,
|
||||
selectedTotalChatsIndex: number,
|
||||
|
||||
Reference in New Issue
Block a user