import { stripe } from "./client" import { PLAN_AMOUNTS } from "./plans" import type { Plan } from "@/types" // Resolve Stripe prices by STABLE LOOKUP KEYS instead of hardcoded price IDs. // // The same lookup keys exist in both test and live mode, so the app finds the // right price for whichever API key is configured — making go-live a pure // key swap with NO price IDs to copy. If a price doesn't exist yet in the // current mode, it's auto-created from PLAN_AMOUNTS on first use, so even the // very first live checkout just works. export const PRICE_LOOKUP_KEYS = { pro_month: "pmn_pro_monthly", pro_year: "pmn_pro_yearly", landlord_month: "pmn_landlord_monthly", landlord_year: "pmn_landlord_yearly", lifetime: "pmn_lifetime", } as const type LookupKey = (typeof PRICE_LOOKUP_KEYS)[keyof typeof PRICE_LOOKUP_KEYS] // How to create each price if it's missing in the current mode (amounts mirror // the single source of truth in plans.ts; yearly = 10× monthly, ~2 months free). const PRICE_SPEC: Record = { [PRICE_LOOKUP_KEYS.pro_month]: { product: "Pro", dollars: PLAN_AMOUNTS.pro, interval: "month" }, [PRICE_LOOKUP_KEYS.pro_year]: { product: "Pro", dollars: PLAN_AMOUNTS.pro * 10, interval: "year" }, [PRICE_LOOKUP_KEYS.landlord_month]: { product: "Landlord", dollars: PLAN_AMOUNTS.landlord, interval: "month" }, [PRICE_LOOKUP_KEYS.landlord_year]: { product: "Landlord", dollars: PLAN_AMOUNTS.landlord * 10, interval: "year" }, [PRICE_LOOKUP_KEYS.lifetime]: { product: "Lifetime", dollars: PLAN_AMOUNTS.lifetime, interval: null }, } // Per-process cache. A deployment runs with a single API key, so test and live // never share a process — no cross-mode leakage. const cache = new Map() async function resolveByLookup(key: LookupKey): Promise { const cached = cache.get(key) if (cached) return cached const existing = await stripe.prices.list({ lookup_keys: [key], active: true, limit: 1 }) if (existing.data[0]) { cache.set(key, existing.data[0].id) return existing.data[0].id } // Not present in this mode yet — create the product + price on the fly so the // first checkout after a key swap succeeds without any manual setup. const spec = PRICE_SPEC[key] try { const product = await stripe.products.create({ name: `Property Management Network — ${spec.product}`, }) const price = await stripe.prices.create({ product: product.id, currency: "usd", unit_amount: Math.round(spec.dollars * 100), lookup_key: key, transfer_lookup_key: true, ...(spec.interval ? { recurring: { interval: spec.interval } } : {}), }) cache.set(key, price.id) return price.id } catch { // Lost a race with a concurrent checkout — re-read and use the winner. const retry = await stripe.prices.list({ lookup_keys: [key], active: true, limit: 1 }) if (retry.data[0]) { cache.set(key, retry.data[0].id) return retry.data[0].id } return undefined } } /** Resolve the Stripe price id for a plan + interval (auto-provisions if needed). */ export async function resolvePriceId(plan: Plan, interval: "month" | "year"): Promise { switch (plan) { case "lifetime": return resolveByLookup(PRICE_LOOKUP_KEYS.lifetime) case "pro": return resolveByLookup(interval === "year" ? PRICE_LOOKUP_KEYS.pro_year : PRICE_LOOKUP_KEYS.pro_month) case "landlord": return resolveByLookup(interval === "year" ? PRICE_LOOKUP_KEYS.landlord_year : PRICE_LOOKUP_KEYS.landlord_month) default: return undefined } }