diff --git a/src/app/api/admin/reconcile-subscriptions/route.ts b/src/app/api/admin/reconcile-subscriptions/route.ts index 47af079..caadf10 100644 --- a/src/app/api/admin/reconcile-subscriptions/route.ts +++ b/src/app/api/admin/reconcile-subscriptions/route.ts @@ -67,12 +67,13 @@ export async function POST(request: Request) { const entity = ((event.payload as Record)?.payload as Record) ?.subscription as Record | undefined; const ent = (entity as { entity?: Record })?.entity; - const toDate = (v: unknown) => { + // ISO strings — postgres.js binds timestamps as strings, not Date objects. + const toISO = (v: unknown) => { const n = Number(v); - return Number.isFinite(n) && n > 0 ? new Date(n * 1000) : null; + return Number.isFinite(n) && n > 0 ? new Date(n * 1000).toISOString() : null; }; - const currentStart = toDate(ent?.current_start); - const currentEnd = toDate(ent?.current_end); + const currentStart = toISO(ent?.current_start); + const currentEnd = toISO(ent?.current_end); const customerId = (ent?.customer_id as string) ?? null; if (grant) { diff --git a/src/app/api/webhooks/razorpay/route.ts b/src/app/api/webhooks/razorpay/route.ts index 407da59..6cc2186 100644 --- a/src/app/api/webhooks/razorpay/route.ts +++ b/src/app/api/webhooks/razorpay/route.ts @@ -36,9 +36,12 @@ const REVOKE_EVENTS: Record = { "subscription.paused": "paused", }; -function unixToDate(v: unknown): Date | null { +// Razorpay sends unix seconds. Return an ISO STRING (not a Date) — postgres.js +// in this repo binds timestamps as strings via the custom serializer; passing a +// raw Date object throws ERR_INVALID_ARG_TYPE. +function unixToISO(v: unknown): string | null { const n = typeof v === "number" ? v : Number(v); - return Number.isFinite(n) && n > 0 ? new Date(n * 1000) : null; + return Number.isFinite(n) && n > 0 ? new Date(n * 1000).toISOString() : null; } export async function POST(req: Request) { @@ -124,8 +127,8 @@ export async function POST(req: Request) { return new NextResponse("ok (unknown subscription)", { status: 200 }); } - const currentStart = unixToDate(sub?.current_start); - const currentEnd = unixToDate(sub?.current_end); + const currentStart = unixToISO(sub?.current_start); + const currentEnd = unixToISO(sub?.current_end); const customerId = (sub?.customer_id as string) ?? null; const grantStatus = GRANT_EVENTS[eventType]; @@ -144,11 +147,13 @@ export async function POST(req: Request) { // paused is a GRANT_EVENTS key? No — paused is in REVOKE. resumed→active. await grantPremium(row.family_id, grantStatus); } else if (revokeStatus) { - const endedAt = revokeStatus === "paused" ? null : new Date(); + const nowIso = new Date().toISOString(); + const endedAt = revokeStatus === "paused" ? null : nowIso; + const cancelledAt = revokeStatus === "cancelled" ? nowIso : null; await sql` UPDATE family_subscriptions SET status = ${revokeStatus}::subscription_status_enum, - cancelled_at = ${revokeStatus === "cancelled" ? new Date() : null}, + cancelled_at = ${cancelledAt}, ended_at = COALESCE(${endedAt}, ended_at), updated_at = NOW() WHERE id = ${row.id}