import { upsertSubscription } from "../subscription"; import { planFromPaypalPlan } from "../catalog"; import type { PlanKey } from "../plans"; interface PaypalResource { id?: string; plan_id?: string; custom_id?: string; billing_info?: { next_billing_time?: string }; } interface PaypalEvent { event_type: string; resource: PaypalResource; } function parseCustom( custom?: string ): { subjectId: string; subjectType: "user" | "organization"; plan: PlanKey } | null { if (!custom) return null; try { return JSON.parse(custom); } catch { return null; } } async function sync(resource: PaypalResource, status: string) { const subId = resource.id; if (!subId) return; const custom = parseCustom(resource.custom_id); const planFromId = resource.plan_id ? planFromPaypalPlan(resource.plan_id) : null; const plan = (custom?.plan || planFromId || "free") as PlanKey; const referenceId = custom?.subjectId; if (!referenceId) { console.warn("[paypal] subscription without custom subjectId, skipping", subId); return; } await upsertSubscription({ provider: "paypal", referenceId, plan, status, paypalSubscriptionId: subId, paypalPlanId: resource.plan_id ?? null, periodEnd: resource.billing_info?.next_billing_time ? new Date(resource.billing_info.next_billing_time) : null, }); } export async function handlePaypalEvent(event: PaypalEvent): Promise { switch (event.event_type) { case "BILLING.SUBSCRIPTION.ACTIVATED": case "BILLING.SUBSCRIPTION.UPDATED": case "BILLING.SUBSCRIPTION.RE-ACTIVATED": await sync(event.resource, "active"); break; case "BILLING.SUBSCRIPTION.SUSPENDED": await sync(event.resource, "paused"); break; case "BILLING.SUBSCRIPTION.CANCELLED": case "BILLING.SUBSCRIPTION.EXPIRED": await sync(event.resource, "canceled"); break; default: break; } }