Public Access
1
0
Files
leads4less/server/src/config/env.ts
T
pguerrerox 5508e15da1 feat: launch Stripe billing flows with lifecycle hardening and analytics
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
2026-05-22 22:55:04 +00:00

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());
}