80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
|
import type { BlitzApiHandler } from "blitz";
|
||
|
import { NotFoundError } from "blitz";
|
||
|
import { z } from "zod";
|
||
|
|
||
|
import type { ApiError } from "../../core/types";
|
||
|
import db from "db";
|
||
|
import appLogger from "../../../integrations/logger";
|
||
|
|
||
|
const logger = appLogger.child({ module: "subscription-cancelled" });
|
||
|
|
||
|
export const subscriptionCancelled: BlitzApiHandler = async (req, res) => {
|
||
|
const validationResult = bodySchema.safeParse(req.body);
|
||
|
if (!validationResult.success) {
|
||
|
const statusCode = 400;
|
||
|
const apiError: ApiError = {
|
||
|
statusCode,
|
||
|
errorMessage: "Body is malformed",
|
||
|
};
|
||
|
logger.error(validationResult.error, "/api/subscription/webhook");
|
||
|
|
||
|
res.status(statusCode).send(apiError);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const body = validationResult.data;
|
||
|
const paddleSubscriptionId = body.subscription_id;
|
||
|
const subscription = await db.subscription.findFirst({ where: { paddleSubscriptionId } });
|
||
|
if (!subscription) {
|
||
|
throw new NotFoundError();
|
||
|
}
|
||
|
|
||
|
const lastEventTime = new Date(body.event_time);
|
||
|
const isEventOlderThanLastUpdate = subscription.lastEventTime > lastEventTime;
|
||
|
if (isEventOlderThanLastUpdate) {
|
||
|
res.status(200).end();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const paddleCheckoutId = body.checkout_id;
|
||
|
const planId = body.subscription_plan_id;
|
||
|
const status = body.status;
|
||
|
const currency = body.currency;
|
||
|
const unitPrice = body.unit_price;
|
||
|
|
||
|
await db.subscription.update({
|
||
|
where: { paddleSubscriptionId },
|
||
|
data: {
|
||
|
paddleSubscriptionId,
|
||
|
paddlePlanId: planId,
|
||
|
paddleCheckoutId,
|
||
|
status,
|
||
|
lastEventTime,
|
||
|
currency,
|
||
|
unitPrice,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
return res.status(200).end();
|
||
|
};
|
||
|
|
||
|
const bodySchema = z.object({
|
||
|
alert_id: z.string(),
|
||
|
alert_name: z.string(),
|
||
|
cancellation_effective_date: z.string(),
|
||
|
checkout_id: z.string(),
|
||
|
currency: z.string(),
|
||
|
email: z.string(),
|
||
|
event_time: z.string(),
|
||
|
linked_subscriptions: z.string(),
|
||
|
marketing_consent: z.string(),
|
||
|
passthrough: z.string(),
|
||
|
quantity: z.string(),
|
||
|
status: z.enum(["active", "trialing", "past_due", "paused", "deleted"]),
|
||
|
subscription_id: z.string(),
|
||
|
subscription_plan_id: z.string(),
|
||
|
unit_price: z.string(),
|
||
|
user_id: z.string(),
|
||
|
p_signature: z.string(),
|
||
|
});
|