(billing) Automatic usage-based billing (#924)

BREAKING CHANGE: Stripe environment variables simplified. Check out the
new configs to adapt your existing system.

Closes #906





<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
### Summary by CodeRabbit

**New Features:**
- Introduced a usage-based billing system, providing more flexibility
and options for users.
- Integrated with Stripe for a smoother and more secure payment process.
- Enhanced the user interface with improvements to the billing,
workspace, and pricing pages for a more intuitive experience.

**Improvements:**
- Simplified the billing logic, removing additional chats and yearly
billing for a more streamlined user experience.
- Updated email notifications to keep users informed about their usage
and limits.
- Improved pricing and currency formatting for better clarity and
understanding.

**Testing:**
- Updated tests and specifications to ensure the reliability of new
features and improvements.

**Note:** These changes aim to provide a more flexible and user-friendly
billing system, with clearer pricing and improved notifications. Users
should find the new system more intuitive and easier to navigate.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Baptiste Arnaud
2023-10-17 08:03:30 +02:00
committed by GitHub
parent a8c2deb258
commit 797751b418
55 changed files with 1589 additions and 1497 deletions

View File

@@ -46,23 +46,22 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
const metadata = session.metadata as unknown as
| {
plan: 'STARTER' | 'PRO'
additionalChats: string
workspaceId: string
userId: string
}
| { claimableCustomPlanId: string; userId: string }
if ('plan' in metadata) {
const { workspaceId, plan, additionalChats } = metadata
if (!workspaceId || !plan || !additionalChats)
const { workspaceId, plan } = metadata
if (!workspaceId || !plan)
return res
.status(500)
.send({ message: `Couldn't retrieve valid metadata` })
const workspace = await prisma.workspace.update({
where: { id: workspaceId },
data: {
plan,
stripeId: session.customer as string,
additionalChatsIndex: parseInt(additionalChats),
isQuarantined: false,
},
include: {
@@ -84,7 +83,6 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
userId: user.id,
data: {
plan,
additionalChatsIndex: parseInt(additionalChats),
},
},
])
@@ -119,7 +117,6 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
userId,
data: {
plan: Plan.CUSTOM,
additionalChatsIndex: 0,
},
},
])
@@ -148,7 +145,6 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
},
data: {
plan: Plan.FREE,
additionalChatsIndex: 0,
customChatsLimit: null,
customStorageLimit: null,
customSeatsLimit: null,
@@ -172,7 +168,6 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
userId: user.id,
data: {
plan: Plan.FREE,
additionalChatsIndex: 0,
},
},
])

View File

@@ -8,7 +8,7 @@ import {
} from '@typebot.io/lib/api'
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
import { sendWorkspaceMemberInvitationEmail } from '@typebot.io/emails'
import { isSeatsLimitReached } from '@typebot.io/lib/pricing'
import { getSeatsLimit } from '@typebot.io/lib/billing/getSeatsLimit'
import { env } from '@typebot.io/env'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
@@ -37,11 +37,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}),
])
if (
isSeatsLimitReached({
existingMembersAndInvitationsCount:
existingMembersCount + existingInvitationsCount,
...workspace,
})
getSeatsLimit(workspace) <=
existingMembersCount + existingInvitationsCount
)
return res.status(400).send('Seats limit reached')
if (existingUser) {