Improve and update billing flow
This commit updates various components in the billing flow due to a new schema that supports multiple line items per plan. The added flexibility rendered 'line-items-mapper.ts' redundant, which has been removed. Additionally, webhooks have been created for handling account membership insertions and deletions, as well as handling subscription deletions when an account is deleted. This message also introduces a new service to handle sending out invitation emails. Lastly, the validation of the billing provider has been improved for increased security and stability.
This commit is contained in:
@@ -17,9 +17,11 @@ NEXT_PUBLIC_BILLING_PROVIDER=stripe
|
||||
# SUPABASE
|
||||
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
||||
|
||||
NEXT_PUBLIC_REQUIRE_EMAIL_CONFIRMATION=true
|
||||
|
||||
## THIS IS FOR DEVELOPMENT ONLY - DO NOT USE IN PRODUCTION
|
||||
SUPABASE_WEBHOOK_SECRET=WEBHOOKSECRET
|
||||
|
||||
EMAIL_SENDER=test@makerkit.dev
|
||||
EMAIL_PORT=54325
|
||||
EMAIL_HOST=localhost
|
||||
|
||||
@@ -37,7 +37,7 @@ export function PersonalAccountCheckoutForm() {
|
||||
|
||||
// Otherwise, render the plan picker component
|
||||
return (
|
||||
<div className={'mx-auto w-full max-w-2xl'}>
|
||||
<div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Manage your Plan</CardTitle>
|
||||
|
||||
@@ -40,26 +40,24 @@ async function PersonalAccountBillingPage() {
|
||||
/>
|
||||
|
||||
<PageBody>
|
||||
<div className={'mx-auto w-full max-w-2xl'}>
|
||||
<div className={'flex flex-col space-y-8'}>
|
||||
<If
|
||||
condition={subscription}
|
||||
fallback={<PersonalAccountCheckoutForm />}
|
||||
>
|
||||
{(subscription) => (
|
||||
<CurrentPlanCard
|
||||
subscription={subscription}
|
||||
config={billingConfig}
|
||||
/>
|
||||
)}
|
||||
</If>
|
||||
<div className={'flex flex-col space-y-8'}>
|
||||
<If
|
||||
condition={subscription}
|
||||
fallback={<PersonalAccountCheckoutForm />}
|
||||
>
|
||||
{(subscription) => (
|
||||
<CurrentPlanCard
|
||||
subscription={subscription}
|
||||
config={billingConfig}
|
||||
/>
|
||||
)}
|
||||
</If>
|
||||
|
||||
<If condition={customerId}>
|
||||
<form action={createPersonalAccountBillingPortalSession}>
|
||||
<BillingPortalCard />
|
||||
</form>
|
||||
</If>
|
||||
</div>
|
||||
<If condition={customerId}>
|
||||
<form action={createPersonalAccountBillingPortalSession}>
|
||||
<BillingPortalCard />
|
||||
</form>
|
||||
</If>
|
||||
</div>
|
||||
</PageBody>
|
||||
</>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { redirect } from 'next/navigation';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getLineItemsFromPlanId } from '@kit/billing';
|
||||
import { getBillingGatewayProvider } from '@kit/billing-gateway';
|
||||
import { requireUser } from '@kit/supabase/require-user';
|
||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export function POST(request: Request) {
|
||||
console.log(request);
|
||||
}
|
||||
20
apps/web/app/api/database/webhook/route.ts
Normal file
20
apps/web/app/api/database/webhook/route.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DatabaseWebhookHandlerService } from '@kit/database-webhooks';
|
||||
|
||||
const webhooksSecret = z
|
||||
.string({
|
||||
description: `The secret used to verify the webhook signature`,
|
||||
})
|
||||
.min(1)
|
||||
.parse(process.env.SUPABASE_DB_WEBHOOK_SECRET);
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const service = new DatabaseWebhookHandlerService();
|
||||
|
||||
await service.handleWebhook(request, webhooksSecret);
|
||||
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inter as SansFont } from 'next/font/google';
|
||||
import { cookies } from 'next/headers';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
|
||||
import { Toaster } from '@kit/ui/sonner';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
@@ -27,6 +27,8 @@ export default async function RootLayout({
|
||||
|
||||
return (
|
||||
<html lang={language} className={getClassName()}>
|
||||
<CsrfTokenMeta />
|
||||
|
||||
<body>
|
||||
<RootProviders lang={language}>{children}</RootProviders>
|
||||
<Toaster richColors={false} />
|
||||
@@ -70,3 +72,9 @@ export const metadata = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function CsrfTokenMeta() {
|
||||
const csrf = headers().get('x-csrf-token') ?? '';
|
||||
|
||||
return <meta content={csrf} name="csrf-token" />;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const INTERNAL_PACKAGES = [
|
||||
'@kit/billing-gateway',
|
||||
'@kit/stripe',
|
||||
'@kit/email-templates',
|
||||
'@kit/database-webhooks'
|
||||
];
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@kit/auth": "workspace:^",
|
||||
"@kit/billing": "workspace:^",
|
||||
"@kit/billing-gateway": "workspace:^",
|
||||
"@kit/database-webhooks": "workspace:^",
|
||||
"@kit/email-templates": "workspace:^",
|
||||
"@kit/i18n": "workspace:^",
|
||||
"@kit/mailers": "workspace:^",
|
||||
|
||||
Reference in New Issue
Block a user