import { env } from "cloudflare:workers"; import { stripeEnabled } from "./billing/config"; import { customerFor } from "./billing/checkout"; import { billingSettings } from "./metering/settings"; const SUPPORTER_CACHE_SECONDS = 10 * 60; /** "Not now" on the Pro nudge hides it for about six months. */ export const NUDGE_SNOOZE_SECONDS = 182 * 24 * 60 * 60; const supporterKey = (accountId: string) => `supporter:v1:${accountId}`; const nudgeKey = (userId: string) => `pro-nudge-dismissed:v1:${userId}`; async function computeSupporter(accountId: string): Promise { const [settings, customer] = await Promise.all([billingSettings(accountId), customerFor(accountId)]); return settings.plan.id === "pro" || customer !== null; } /** * Whether an account is on Pro or has a card on file with Stripe. Supporters get the Pro badge and never * see the upgrade nudge. Always false without Stripe billing (self-hosted instances have nothing to sell). */ export async function isSupporter(accountId: string): Promise { if (!stripeEnabled()) return false; const cached = await env.KV.get(supporterKey(accountId)); if (cached !== null) return cached === "1"; const value = await computeSupporter(accountId); await env.KV.put(supporterKey(accountId), value ? "1" : "0", { expirationTtl: SUPPORTER_CACHE_SECONDS }); return value; } /** Drops the cached supporter flag, e.g. right after checkout or a subscription change. */ export async function forgetSupporter(accountId: string): Promise { await env.KV.delete(supporterKey(accountId)); } export async function nudgeDismissed(userId: string): Promise { return (await env.KV.get(nudgeKey(userId))) !== null; } export async function dismissNudge(userId: string): Promise { await env.KV.put(nudgeKey(userId), String(Date.now()), { expirationTtl: NUDGE_SNOOZE_SECONDS }); } /** The gentle upgrade nudge: only on billed instances, only for non-supporters who haven't said "not now". */ export async function shouldNudge(userId: string): Promise { if (!stripeEnabled()) return false; const [supporter, dismissed] = await Promise.all([isSupporter(userId), nudgeDismissed(userId)]); return !supporter && !dismissed; }