feat: whyrating - initial project from turbostarter boilerplate

This commit is contained in:
Alejandro Gutiérrez
2026-02-04 01:54:52 +01:00
commit 5cdc07cd39
1618 changed files with 338230 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
import { HttpStatusCode } from "@turbostarter/shared/constants";
import { logger } from "@turbostarter/shared/logger";
import { HttpException } from "@turbostarter/shared/utils";
import { checkoutStatusChangeHandler } from "../checkout";
import { env } from "../env";
import { subscriptionStatusChangeHandler } from "../subscription";
import { LEMON_SQUEEZY_SIGNATURE_HEADER } from "./constants";
import { validateSignature } from "./signing";
import { webhookHasData, webhookHasMeta } from "./type-guards";
import type { BillingProviderStrategy } from "../../types";
export const webhookHandler: BillingProviderStrategy["webhookHandler"] = async (
req,
callbacks,
) => {
const body = await req.text();
const sig = req.headers.get(LEMON_SQUEEZY_SIGNATURE_HEADER);
if (!sig) {
throw new HttpException(HttpStatusCode.BAD_REQUEST, {
code: "billing:error.webhook.signatureNotFound",
});
}
await validateSignature(sig, env.LEMON_SQUEEZY_SIGNING_SECRET, body);
const data = JSON.parse(body);
if (!webhookHasMeta(data)) {
throw new HttpException(HttpStatusCode.BAD_REQUEST, {
code: "billing:error.webhook.metaInvalid",
});
}
const type = data.meta.event_name;
logger.info(`🔔 Webhook received: ${type}`);
if (!webhookHasData(data)) {
throw new HttpException(HttpStatusCode.BAD_REQUEST, {
code: "billing:error.webhook.dataInvalid",
});
}
await callbacks?.onEvent?.(data);
switch (type) {
case "subscription_created":
await callbacks?.onSubscriptionCreated?.(data.data.id);
await subscriptionStatusChangeHandler({
id: data.data.id,
});
break;
case "subscription_updated":
await callbacks?.onSubscriptionUpdated?.(data.data.id);
await subscriptionStatusChangeHandler({
id: data.data.id,
});
break;
case "subscription_expired":
await callbacks?.onSubscriptionDeleted?.(data.data.id);
await subscriptionStatusChangeHandler({
id: data.data.id,
});
break;
case "order_created":
await callbacks?.onCheckoutSessionCompleted?.(data.data.id);
await checkoutStatusChangeHandler({
id: data.data.id,
});
break;
}
return new Response(JSON.stringify({ received: true }), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
};