shellphone.app/app/settings/webhook-handlers/subscription-payment-succeeded.ts

101 lines
2.7 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-payment-succeeded" });
export const subscriptionPaymentSucceededHandler: 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 nextBillDate = new Date(body.next_bill_date);
const status = body.status;
const currency = body.currency;
const unitPrice = body.unit_price;
await db.subscription.update({
where: { paddleSubscriptionId },
data: {
paddleSubscriptionId,
paddlePlanId: planId,
paddleCheckoutId,
nextBillDate,
status,
lastEventTime,
currency,
unitPrice,
},
});
return res.status(200).end();
};
const bodySchema = z.object({
alert_id: z.string(),
alert_name: z.string(),
balance_currency: z.string(),
balance_earnings: z.string(),
balance_fee: z.string(),
balance_gross: z.string(),
balance_tax: z.string(),
checkout_id: z.string(),
country: z.string(),
coupon: z.string(),
currency: z.string(),
customer_name: z.string(),
earnings: z.string(),
email: z.string(),
event_time: z.string(),
fee: z.string(),
initial_payment: z.string(),
instalments: z.string(),
marketing_consent: z.string(),
next_bill_date: z.string(),
next_payment_amount: z.string(),
order_id: z.string(),
passthrough: z.string(),
payment_method: z.string(),
payment_tax: z.string(),
plan_name: z.string(),
quantity: z.string(),
receipt_url: z.string(),
sale_gross: z.string(),
status: z.enum(["active", "trialing", "past_due", "paused", "deleted"]),
subscription_id: z.string(),
subscription_payment_id: z.string(),
subscription_plan_id: z.string(),
unit_price: z.string(),
user_id: z.string(),
p_signature: z.string(),
});