5508e15da1
add Stripe checkout, portal, webhook ingestion, and idempotent event persistence add billing lifecycle state (grace/sync/timeline/admin visibility) and stronger entitlement handling add analytics event tracking and admin summary APIs plus account/pricing UI integration
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { existsSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import dotenv from 'dotenv';
|
|
import { z } from 'zod';
|
|
|
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
const envFiles = [
|
|
path.join(projectRoot, '.env.local'),
|
|
path.join(projectRoot, '.env'),
|
|
];
|
|
|
|
for (const envFile of envFiles) {
|
|
if (existsSync(envFile)) {
|
|
dotenv.config({ path: envFile, override: false });
|
|
}
|
|
}
|
|
|
|
const envSchema = z.object({
|
|
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
APP_HOST: z.string().default('0.0.0.0'),
|
|
APP_PORT: z.coerce.number().int().positive().default(4000),
|
|
APP_ORIGIN: z.string().default('http://localhost:3000'),
|
|
DATABASE_URL: z.string().min(1, 'DATABASE_URL is required'),
|
|
COOKIE_SECRET: z.string().min(1, 'COOKIE_SECRET is required'),
|
|
GOOGLE_MAPS_SERVER_KEY: z.string().optional(),
|
|
PG_BOSS_SCHEMA: z.string().default('pgboss'),
|
|
SESSION_TTL_DAYS: z.coerce.number().int().positive().default(30),
|
|
STRIPE_SECRET_KEY: z.string().optional(),
|
|
STRIPE_PUBLISHABLE_KEY: z.string().optional(),
|
|
STRIPE_WEBHOOK_SECRET: z.string().optional(),
|
|
STRIPE_PRICE_STARTER_MONTHLY: z.string().optional(),
|
|
STRIPE_PRICE_STARTER_ANNUAL: z.string().optional(),
|
|
STRIPE_PRICE_GROWTH_MONTHLY: z.string().optional(),
|
|
STRIPE_PRICE_GROWTH_ANNUAL: z.string().optional(),
|
|
STRIPE_PRICE_PRO_MONTHLY: z.string().optional(),
|
|
STRIPE_PRICE_PRO_ANNUAL: z.string().optional(),
|
|
STRIPE_PRICE_EXPORT_PACK_10K: z.string().optional(),
|
|
STRIPE_PRICE_EXPORT_PACK_50K: z.string().optional(),
|
|
STRIPE_BILLING_PORTAL_CONFIGURATION_ID: z.string().optional(),
|
|
BILLING_ADMIN_EMAILS: z.string().optional(),
|
|
});
|
|
|
|
export type AppEnv = z.infer<typeof envSchema>;
|
|
|
|
let cachedEnv: AppEnv | null = null;
|
|
|
|
export function getEnv(): AppEnv {
|
|
if (cachedEnv) {
|
|
return cachedEnv;
|
|
}
|
|
|
|
cachedEnv = envSchema.parse(process.env);
|
|
return cachedEnv;
|
|
}
|
|
|
|
export function isBillingAdminEmail(email: string) {
|
|
const allowlist = getEnv().BILLING_ADMIN_EMAILS;
|
|
|
|
if (!allowlist) {
|
|
return false;
|
|
}
|
|
|
|
return allowlist
|
|
.split(',')
|
|
.map((entry) => entry.trim().toLowerCase())
|
|
.filter(Boolean)
|
|
.includes(email.trim().toLowerCase());
|
|
}
|