2
0

(billing) Implement custom plan

This commit is contained in:
Baptiste Arnaud
2022-10-27 11:32:21 +02:00
committed by Baptiste Arnaud
parent 3f7dc79918
commit 385853ca3c
23 changed files with 395 additions and 68 deletions

View File

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

View File

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

View File

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

View File

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

View File

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