metatrondelivery/app/services/billing.server.ts
metatroncubeswdev d98b29ec76
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
chore: add UNLOCK_ALL_FEATURES env flag to bypass plan gating for testing
getShopTier() returns "pro" when UNLOCK_ALL_FEATURES=true, so every
plan-gated screen (Zones, Rates, Product rules, Dashboard) and the per-tier
location cap open up regardless of the shop's real subscription. Single
choke point — every tierAtLeast()/locationLimitFor() call derives from
getShopTier(). Default off; documented in .env.example as production-unsafe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 02:13:49 -04:00

38 lines
1.5 KiB
TypeScript

import db from "../db.server";
import { locationLimitFor, type Tier } from "../lib/billing-plans";
export type { Tier };
export { tierAtLeast, tierFromPlanName, locationLimitFor, LOCATION_LIMITS, PLAN_PRICING } from "../lib/billing-plans";
// Test/dev escape hatch: set UNLOCK_ALL_FEATURES=true in .env to make every
// shop resolve as the top tier, so plan-gated screens (Zones, Rates,
// Product rules, Dashboard) and the per-tier location cap are all open
// regardless of the shop's real subscription. MUST stay off in production —
// it bypasses billing entirely.
const UNLOCK_ALL_FEATURES = process.env.UNLOCK_ALL_FEATURES === "true";
export async function getShopTier(shopDomain: string): Promise<Tier> {
if (UNLOCK_ALL_FEATURES) return "pro";
const shop = await db.shop.findUnique({ where: { shopDomain }, select: { tier: true } });
return (shop?.tier as Tier | undefined) ?? "free";
}
export async function setShopTier(shopDomain: string, tier: Tier): Promise<void> {
await db.shop.upsert({
where: { shopDomain },
create: { shopDomain, tier },
update: { tier },
});
}
export async function countLocations(shopDomain: string): Promise<number> {
return db.location.count({ where: { shopDomain } });
}
export async function canAddLocation(shopDomain: string): Promise<boolean> {
const tier = await getShopTier(shopDomain);
const limit = locationLimitFor(tier);
if (limit === null) return true;
return (await countLocations(shopDomain)) < limit;
}