# ============================================== # OpenRouter / DeepSeek AI Vision Keys # ============================================== # OpenRouter API Key for GPT-5.6 Luna ($0.10 / $0.60 per 1M) OPENROUTER_API_KEY=your_openrouter_api_key_here OPENROUTER_MODEL=openai/gpt-5.6-luna # DeepSeek Direct API DEEPSEEK_API_KEY=your_deepseek_api_key_here DEEPSEEK_BASE_URL=https://api.deepseek.com # Fallback Vision Provider (OpenAI GPT-4o-mini / Gemini Flash) OPENAI_API_KEY=your_openai_api_key_here GEMINI_API_KEY=your_gemini_api_key_here # ============================================== # Database (PostgreSQL - Neon / Supabase) # ============================================== # Local-First IndexedDB is active by default. Optional PostgreSQL for production: DATABASE_URL=postgresql://user:password@localhost:5432/receipt_scanner # Least-privilege runtime role (recommended for production). # Provision it with scripts/db-permissions.sql — via `node # scripts/apply-db-permissions.mjs`, or automatically on a fresh volume through # the docker-compose init mount — then point the app's RUNTIME connection at # it. The role (receipt_app) has CONNECT + schema USAGE + table/sequence DML # only: no superuser, no CREATEDB/CREATEROLE, no DDL. Migrations and schema # init still require the OWNER DATABASE_URL above (they run DDL), so run those # with the owner URL and the app with the restricted URL. # APP_DATABASE_URL=postgresql://receipt_app:receipt_app_secure_password@localhost:5432/receipt_scanner # APP_DATABASE_PASSWORD=receipt_app_secure_password # ============================================== # Storage (Cloudflare R2 / S3 - Optional) # ============================================== R2_ACCOUNT_ID=your_cloudflare_account_id R2_ACCESS_KEY_ID=your_r2_access_key R2_SECRET_ACCESS_KEY=your_r2_secret_key R2_BUCKET_NAME=receipt-images R2_PUBLIC_URL=https://your-bucket-url.com # ============================================== # Payments & Webhooks (Stripe) # ============================================== STRIPE_SECRET_KEY=sk_test_... STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_WEEKLY_PRICE_ID=price_... STRIPE_ANNUAL_PRICE_ID=price_... STRIPE_LIFETIME_PRICE_ID=price_... # Prices are decided server-side from src/lib/billing/pricing.ts. When a Price # ID above is set, Checkout uses it; otherwise the catalog amount is charged. # The webhook cross-checks the paid amount against that same catalog, so a # Price ID must never diverge from the catalog amount for its plan. # ============================================== # Authentication — Google Sign-In (optional) # ============================================== # Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client ID # (type "Web application"). Authorised redirect URI must be exactly: # /api/auth/google/callback # The Google button only renders when both values are present. GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your_client_secret # ============================================== # Authentication — SMTP for confirmation links # ============================================== # Required in production: signup fails loudly without it, because an account # that can never be confirmed must not be created. In development, missing SMTP # makes the confirmation link appear in the server log and in the UI instead. SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=no-reply@yourdomain.com SMTP_PASSWORD=your_smtp_password # true for implicit TLS on port 465; false for STARTTLS on 587. SMTP_SECURE=false MAIL_FROM=ScanReceipts # ============================================== # Discord Sales Notification Bot # ============================================== DISCORD_SALES_WEBHOOK_URL=https://discord.com/api/webhooks/... # ============================================== # App Settings # ============================================== # Inlined into the client bundle at build time and baked into the Docker image. # Pass it as --build-arg NEXT_PUBLIC_APP_URL (docker-compose forwards it via # build.args) so sitemap/robots/canonicals are built against the real domain. # PRODUCTION MUST BE AN https:// URL: the app is HTTPS-only (HSTS via # Strict-Transport-Security), and an http:// value would make browsers refuse # the upgrade promise baked into every response. http://localhost:3000 is fine # for local development only. NEXT_PUBLIC_APP_URL=http://localhost:3000 # ============================================== # Cookie domain — share the session across subdomains # ============================================== # When the dashboard is served on app. and the admin on admin. # (both rewritten by src/middleware.ts), the session, guest, CSRF and OAuth # cookies must be scoped to the parent domain so one login works everywhere. # Leave EMPTY in local development (localhost cookies stay host-only). # Example for production: # COOKIE_DOMAIN=.scan-receipts.app # ============================================== # CORS — cross-origin API access # ============================================== # Comma-separated allowlist of origins (scheme://host[:port]) allowed to call # the API with credentials. The app's own origin (NEXT_PUBLIC_APP_URL) is # always allowed and must NOT be repeated here. Never use "*": the app sends # cookies (guest sessions, admin subdomain) and a wildcard would be rejected by # browsers and is a CSRF risk. Include the admin subdomain origin # (e.g. https://admin.example.com) if it should call the API cross-origin. # Leave empty for same-origin-only access. CORS_ORIGINS= # ============================================== # Umami Analytics (self-hosted, optional) # ============================================== # Inlined into the client bundle at build time (see Dockerfile ARG/ENV) and # also read by next.config.ts to widen the CSP to the script's origin. The # tracking script only renders when BOTH values are set (src/app/(app)/layout.tsx # and src/app/(marketing)/[locale]/layout.tsx). Leave empty to disable. # NEXT_PUBLIC_UMAMI_SRC=https://analytics.yourdomain.com/script.js # NEXT_PUBLIC_UMAMI_ID=your-website-id # ============================================== # Admin Dashboard Access # ============================================== # Comma-separated list of email addresses with admin access ADMIN_EMAILS=admin@example.com