shellphone.app/app/settings/api/webhook/subscription.ts

77 lines
2.7 KiB
TypeScript
Raw Normal View History

import type { BlitzApiHandler } from "blitz";
import type { Queue } from "quirrel/blitz";
import type {
PaddleSdkSubscriptionCancelledEvent,
PaddleSdkSubscriptionCreatedEvent,
PaddleSdkSubscriptionPaymentSucceededEvent,
PaddleSdkSubscriptionUpdatedEvent,
} from "@devoxa/paddle-sdk";
import { PaddleSdkException, PaddleSdkWebhookEventType } from "@devoxa/paddle-sdk";
2021-09-26 22:08:02 +00:00
import type { ApiError } from "app/core/types";
import subscriptionCreatedQueue from "../queue/subscription-created";
import subscriptionPaymentSucceededQueue from "../queue/subscription-payment-succeeded";
import subscriptionCancelledQueue from "../queue/subscription-cancelled";
import subscriptionUpdatedQueue from "../queue/subscription-updated";
import appLogger from "integrations/logger";
import { paddleSdk } from "integrations/paddle";
2021-09-26 22:08:02 +00:00
type Events<TMetadata = { organizationId: string }> =
| PaddleSdkSubscriptionCreatedEvent<TMetadata>
| PaddleSdkSubscriptionUpdatedEvent<TMetadata>
| PaddleSdkSubscriptionCancelledEvent<TMetadata>
| PaddleSdkSubscriptionPaymentSucceededEvent<TMetadata>;
2021-09-26 22:08:02 +00:00
type SupportedEventType = Events["eventType"];
2021-09-26 22:08:02 +00:00
const queues: Record<SupportedEventType, Queue<{ event: Events }>> = {
[PaddleSdkWebhookEventType.SUBSCRIPTION_CREATED]: subscriptionCreatedQueue,
[PaddleSdkWebhookEventType.SUBSCRIPTION_PAYMENT_SUCCEEDED]: subscriptionPaymentSucceededQueue,
[PaddleSdkWebhookEventType.SUBSCRIPTION_CANCELLED]: subscriptionCancelledQueue,
[PaddleSdkWebhookEventType.SUBSCRIPTION_UPDATED]: subscriptionUpdatedQueue,
};
2021-09-26 22:08:02 +00:00
const logger = appLogger.child({ route: "/api/subscription/webhook" });
const webhook: BlitzApiHandler = async (req, res) => {
2021-09-26 22:08:02 +00:00
if (req.method !== "POST") {
const statusCode = 405;
const apiError: ApiError = {
statusCode,
errorMessage: `Method ${req.method} Not Allowed`,
};
logger.error(apiError);
res.setHeader("Allow", ["POST"]);
res.status(statusCode).send(apiError);
return;
}
try {
const event = paddleSdk.parseWebhookEvent(req.body);
const alertName = event.eventType;
logger.info(`Received ${alertName} webhook`);
logger.info(event);
if (isSupportedWebhook(alertName)) {
await queues[alertName].enqueue({ event: event as Events }, { id: event.eventId.toString() });
return res.status(200).end();
}
return res.status(400).end();
} catch (error) {
if (error instanceof PaddleSdkException && error.message.includes(req.body.alert_name)) {
// event implementation is missing such as `subscription_payment_refunded`
return res.status(200).end();
}
2021-09-26 22:08:02 +00:00
throw error;
}
};
export default webhook;
function isSupportedWebhook(eventType: PaddleSdkWebhookEventType): eventType is SupportedEventType {
return Object.keys(queues).includes(eventType);
2021-09-26 22:08:02 +00:00
}