168 lines
4.3 KiB
TypeScript
168 lines
4.3 KiB
TypeScript
import { headers } from 'next/headers';
|
|
import { NextResponse } from 'next/server';
|
|
|
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
|
|
import type { Stripe } from 'stripe';
|
|
|
|
import getSupabaseRouteHandlerClient from '@packages/supabase/route-handler-client';
|
|
|
|
import { Logger } from '@kit/logger';
|
|
import createStripeClient from '@kit/stripe/get-stripe';
|
|
import StripeWebhooks from '@kit/stripe/stripe-webhooks.enum';
|
|
|
|
import { setOrganizationSubscriptionData } from '@/lib/organizations/database/mutations';
|
|
import {
|
|
addSubscription,
|
|
deleteSubscription,
|
|
updateSubscriptionById,
|
|
} from '@/lib/subscriptions/mutations';
|
|
|
|
const STRIPE_SIGNATURE_HEADER = 'stripe-signature';
|
|
|
|
const webhookSecretKey = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
|
|
const logName = 'stripe-webhook';
|
|
|
|
/**
|
|
* @description Handle the webhooks from Stripe related to checkouts
|
|
*/
|
|
export async function POST(request: Request) {
|
|
const signature = headers().get(STRIPE_SIGNATURE_HEADER);
|
|
|
|
Logger.info(`[Stripe] Received Stripe Webhook`);
|
|
|
|
if (!webhookSecretKey) {
|
|
Logger.error(
|
|
{
|
|
name: logName,
|
|
},
|
|
`The variable STRIPE_WEBHOOK_SECRET is unset. Please add the STRIPE_WEBHOOK_SECRET environment variable`,
|
|
);
|
|
|
|
return new Response(null, {
|
|
status: 500,
|
|
});
|
|
}
|
|
|
|
// verify signature header is not missing
|
|
if (!signature) {
|
|
return new Response('Invalid signature', {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
const rawBody = await request.text();
|
|
const stripe = await createStripeClient();
|
|
|
|
// create an Admin client to write to the subscriptions table
|
|
const client = getSupabaseRouteHandlerClient({
|
|
admin: true,
|
|
});
|
|
|
|
try {
|
|
// build the event from the raw body and signature using Stripe
|
|
const event = stripe.webhooks.constructEvent(
|
|
rawBody,
|
|
signature,
|
|
webhookSecretKey,
|
|
);
|
|
|
|
Logger.info(
|
|
{
|
|
name: logName,
|
|
type: event.type,
|
|
},
|
|
`Processing Stripe Webhook...`,
|
|
);
|
|
|
|
switch (event.type) {
|
|
case StripeWebhooks.Completed: {
|
|
const session = event.data.object as Stripe.Checkout.Session;
|
|
const subscriptionId = session.subscription as string;
|
|
|
|
const subscription =
|
|
await stripe.subscriptions.retrieve(subscriptionId);
|
|
|
|
await onCheckoutCompleted(client, session, subscription);
|
|
|
|
break;
|
|
}
|
|
|
|
case StripeWebhooks.SubscriptionDeleted: {
|
|
const subscription = event.data.object as Stripe.Subscription;
|
|
|
|
await deleteSubscription(client, subscription.id);
|
|
|
|
break;
|
|
}
|
|
|
|
case StripeWebhooks.SubscriptionUpdated: {
|
|
const subscription = event.data.object as Stripe.Subscription;
|
|
|
|
await updateSubscriptionById(client, subscription);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
Logger.error(
|
|
{
|
|
error,
|
|
name: logName,
|
|
},
|
|
`Webhook handling failed`,
|
|
);
|
|
|
|
return new Response(null, {
|
|
status: 500,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description When the checkout is completed, we store the order. The
|
|
* subscription is only activated if the order was paid successfully.
|
|
* Otherwise, we have to wait for a further webhook
|
|
*/
|
|
async function onCheckoutCompleted(
|
|
client: SupabaseClient,
|
|
session: Stripe.Checkout.Session,
|
|
subscription: Stripe.Subscription,
|
|
) {
|
|
const organizationUid = getOrganizationUidFromClientReference(session);
|
|
const customerId = session.customer as string;
|
|
|
|
// build organization subscription and set on the organization document
|
|
// we add just enough data in the DB, so we do not query
|
|
// Stripe for every bit of data
|
|
// if you need your DB record to contain further data
|
|
// add it to {@link buildOrganizationSubscription}
|
|
const { error, data } = await addSubscription(client, subscription);
|
|
|
|
if (error) {
|
|
return Promise.reject(
|
|
`Failed to add subscription to the database: ${error}`,
|
|
);
|
|
}
|
|
|
|
return setOrganizationSubscriptionData(client, {
|
|
organizationUid,
|
|
customerId,
|
|
subscriptionId: data.id,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @name getOrganizationUidFromClientReference
|
|
* @description Get the organization UUID from the client reference ID
|
|
* @param session
|
|
*/
|
|
function getOrganizationUidFromClientReference(
|
|
session: Stripe.Checkout.Session,
|
|
) {
|
|
return session.client_reference_id!;
|
|
}
|