173 lines · 6.8 KB
import { env } from "cloudflare:workers";
import Stripe from "stripe";
import { HttpError } from "../errors";
import { PACKS, PRO_PRICE_CENTS } from "~/lib/pricing";
import type { PackDef } from "~/lib/pricing";

/**
 * Stripe is optional: every billing path checks this first, and nothing else in the app requires Stripe.
 * Keys from different modes (a live secret with a test publishable key, say) disable billing entirely and
 * log the reason once per isolate; the rest of the forge keeps serving.
 */
export function stripeEnabled(): boolean {
  return Boolean(env.STRIPE_SECRET_KEY) && !reportedModeError();
}

export function isTestKey(key: string | undefined): boolean {
  return Boolean(key && /^(sk|rk)_test_/.test(key));
}

type KeyMode = "test" | "live";

/** The mode a Stripe key belongs to, from its prefix; null when it has no recognizable prefix. */
export function keyMode(key: string | undefined, kinds: string[]): KeyMode | null {
  const match = key ? new RegExp(`^(${kinds.join("|")})_(test|live)_`).exec(key) : null;
  return match ? (match[2] as KeyMode) : null;
}

/**
 * Why the Stripe configuration is unsafe to run, or null. The secret key must be sk_/rk_ test or live; the
 * publishable key, when set, must be the same mode; STRIPE_WEBHOOK_MODE (whsec_ secrets carry no mode), when
 * set, must match too. Mixing modes would accept events or show prices from the other mode.
 */
export function stripeModeError(
  vars: { secret?: string; publishable?: string; webhookMode?: string } = {
    secret: env.STRIPE_SECRET_KEY,
    publishable: env.STRIPE_PUBLISHABLE_KEY,
    webhookMode: env.STRIPE_WEBHOOK_MODE,
  },
): string | null {
  if (!vars.secret) return null;