72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
import type Stripe from "stripe";
|
|
import { stripe } from "../stripe";
|
|
import { upsertSubscription } from "../subscription";
|
|
import { planFromStripePrice } from "../catalog";
|
|
import type { PlanKey } from "../plans";
|
|
|
|
function normalizeStatus(status: Stripe.Subscription.Status): string {
|
|
switch (status) {
|
|
case "active":
|
|
case "trialing":
|
|
case "past_due":
|
|
case "paused":
|
|
return status;
|
|
default:
|
|
return "canceled"; // canceled | incomplete | incomplete_expired | unpaid
|
|
}
|
|
}
|
|
|
|
async function syncStripeSubscription(
|
|
sub: Stripe.Subscription,
|
|
metadata?: Stripe.Metadata | null
|
|
) {
|
|
const item = sub.items.data[0];
|
|
const priceId = item?.price?.id;
|
|
const mapped = priceId ? planFromStripePrice(priceId) : null;
|
|
const plan = ((metadata?.plan as PlanKey) || mapped?.plan || "free") as PlanKey;
|
|
const referenceId = metadata?.subjectId || sub.metadata?.subjectId;
|
|
if (!referenceId) {
|
|
console.warn("[stripe] subscription without subjectId metadata, skipping", sub.id);
|
|
return;
|
|
}
|
|
|
|
const interval =
|
|
mapped?.interval ?? (item?.price?.recurring?.interval === "year" ? "year" : "month");
|
|
|
|
await upsertSubscription({
|
|
provider: "stripe",
|
|
referenceId,
|
|
plan,
|
|
status: normalizeStatus(sub.status),
|
|
billingInterval: interval,
|
|
stripeCustomerId: typeof sub.customer === "string" ? sub.customer : sub.customer.id,
|
|
stripeSubscriptionId: sub.id,
|
|
periodStart: sub.current_period_start ? new Date(sub.current_period_start * 1000) : null,
|
|
periodEnd: sub.current_period_end ? new Date(sub.current_period_end * 1000) : null,
|
|
cancelAtPeriodEnd: sub.cancel_at_period_end,
|
|
});
|
|
}
|
|
|
|
export async function handleStripeEvent(event: Stripe.Event): Promise<void> {
|
|
switch (event.type) {
|
|
case "checkout.session.completed": {
|
|
const session = event.data.object as Stripe.Checkout.Session;
|
|
if (session.mode !== "subscription" || !session.subscription) break;
|
|
const subId =
|
|
typeof session.subscription === "string" ? session.subscription : session.subscription.id;
|
|
const sub = await stripe().subscriptions.retrieve(subId);
|
|
await syncStripeSubscription(sub, session.metadata);
|
|
break;
|
|
}
|
|
case "customer.subscription.created":
|
|
case "customer.subscription.updated":
|
|
case "customer.subscription.deleted": {
|
|
const sub = event.data.object as Stripe.Subscription;
|
|
await syncStripeSubscription(sub, sub.metadata);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|