Files
myeasycms-v2/apps/web/app/api/stripe/webhook/route.ts
giancarlo bce3479368 Cleanup
2024-03-24 02:23:22 +08:00

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!;
}