import { env } from "cloudflare:workers";
import { first } from "../db";
import { accountDb } from "../scoped-db";
import { meterDef } from "~/lib/meters";
import type { MeterId } from "~/lib/meters";
import { isPlanId, parsePlans } from "~/lib/plans";
import type { Plan } from "~/lib/plans";
import { emailConfigured } from "../email";
export function plans(): Record<string, Plan> {
return parsePlans(env.METERING_PLANS);
}
export type BillingSettings = { plan: Plan; spendingLimitCents: number | null; disabled?: boolean };
const DEFAULT_PAST_DUE_GRACE_DAYS = 14;
const DAY_MS = 86_400_000;
/** Days a past_due subscription keeps Pro (PAST_DUE_GRACE_DAYS, default 14) before usage falls back to Free. */
export function pastDueGraceMs(raw = env.PAST_DUE_GRACE_DAYS): number {
const days = raw === undefined || raw === "" ? DEFAULT_PAST_DUE_GRACE_DAYS : Number(raw);
return (Number.isFinite(days) && days >= 0 ? days : DEFAULT_PAST_DUE_GRACE_DAYS) * DAY_MS;
}
export async function billingSettings(accountId: string): Promise<BillingSettings> {
const all = plans();
if (accountId.startsWith("scratch:")) return { plan: all.scratch, spendingLimitCents: 0 };
const [billing, directory] = await Promise.all([
accountDb(accountId).first<BillingFields>(BILLING_SQL, accountId),
first<DirectoryFields>(directorySql(emailConfigured()), accountId),
]);
const row: BillingRow | null = billing || directory ? { ...EMPTY_BILLING, ...billing, ...directory } : null;
let planId = row?.plan && isPlanId(row.plan) ? row.plan : "free";
// Stripe may leave a subscription past_due indefinitely; Pro compute stops after the grace period anyway.
if (
planId === "pro" &&
!row?.plan_override &&
row?.subscription_status === "past_due" &&
// No recorded start (a row predating the column) never counts as lapsed: its grace starts now.
Date.now() - (row.past_due_since ?? Date.now()) > pastDueGraceMs()