Files
route-commerce/PRODUCTION_DEPLOYMENT_CHECKLIST.md
T
Nora 49b8e27219 chore(supabase): full purge — remove all Supabase references from codebase
- Delete supabase/ directory (config.toml, push-migrations.js,
  ADMIN_CREATE_STOP_FIX.sql, 137 archived migration files)
- Remove @supabase/ssr and @supabase/supabase-js from package.json
- Strip *.supabase.co from next.config.ts image hostnames
- Strip https://*.supabase.co from vercel.json CSP connect-src
- Remove 'supabase/**' ignore from eslint.config.mjs
- Clean supabase references from src/lib/db.ts, vitest.config.ts,
  db/seeds/2026-tuxedo-tour-stops.sql, src/actions/storefront.ts,
  scripts/import-tuxedo-stops.ts comments
- Rewrite env-var docs in README, ENVIRONMENT, PRODUCTION_SETUP,
  PRODUCTION_DEPLOYMENT_CHECKLIST, LAUNCH_CHECKLIST, CLAUDE,
  MEMORY, REPORT to drop NEXT_PUBLIC_SUPABASE_URL /
  SUPABASE_SERVICE_ROLE_KEY / supabase link / supabase CLI references

The canonical migration runner is now scripts/migrate.js (uses pg
directly via DATABASE_URL). Migrations live in db/migrations/. The
Supabase CLI is no longer in the codebase. The only remaining
'@supabase/*' in the dep tree is @supabase/auth-js as a transitive
of @neondatabase/auth (Neon Auth / Better Auth).

Final source scan: grep -rln '@supabase\|rest/v1\|supabase\.co'
src/ tests/ db/ scripts/ next.config.ts vercel.json eslint.config.mjs
package.json returns zero. Verification: npm install clean
(11 added, 21 removed, 12 changed), tsc shows same 17 pre-existing
errors (Stripe dahlia API version + fetch preconnect mocks),
vitest 172/175 (3 pre-existing failures in getAdminUser.test.ts),
lint shows same 14 pre-existing errors. Live-tested: dev server
boots in 248ms, public storefronts (/) (/login) (/pricing) (/tuxedo)
(/indian-river-direct) all return 200; /admin/v2?demo=1 reaches 200
after dev_session redirect; storefront renders real brand content.
2026-06-25 17:48:32 -06:00

17 KiB
Raw Blame History

Route Commerce — Production Deployment Checklist

Platform Version: 1.6 Last Updated: 2026-05-13 Environment: Next.js 16 (App Router) · Postgres (direct via pg) · Neon Auth (Better Auth) · Stripe · Square · Resend · FedEx


1. Migration Order

Apply migrations in number order. All numbered 001092 are required.

# Push all migrations (sequential, no Supabase CLI required)
npm run migrate:one 001
npm run migrate:one 002
# ... through ...
npm run migrate:one 092

# Or push all at once
npm run migrate

Key migrations (last 15):

# File Purpose
078 wholesale_deposit_guard.sql Adds amount, payment_method to wholesale_deposits
079 resend_analytics.sql Adds Resend webhook endpoint + analytics tables
080 communication_segments.sql Adds communication_segments table
081 stop_blast_campaign.sql Adds stop_blast_campaign RPC
082 brand_name_in_campaign_email.sql Adds brand_name column to communication_campaigns
083 shipping_settings.sql Adds shipping_settings table
084 shipments.sql Adds shipments table for FedEx tracking
085 brand_settings.sql Adds brand_settings table (core)
086 brand_settings_email_integration.sql Adds email/webhook fields to brand_settings
087 brand_logos_bucket.sql Adds brand-logos storage bucket
088 brand_features.sql Adds brand_features table + RPCs for add-on system
089 ai_providers_custom_integrations.sql Adds AI provider credentials storage
090 storefront_settings.sql Adds storefront customization fields + get_brand_settings_by_slug RPC
091 091_brand_plan_tier.sql Adds plan_tier, limits, stripe_customer_id, get_brand_plan_info RPC
092 092_stripe_subscriptions.sql Adds stripe_subscription_id/status/current_period_end, set_brand_subscription, get_brand_subscription RPCs

Note: Migration 092 adds Stripe subscription tracking. Migration 091 adds plan tier billing. Apply both before deploying billing changes.


2. Required Environment Variables

Copy .env.local.example to .env.local and fill in all values.

Core Platform

Variable Required Description
DATABASE_URL Yes Postgres connection string (e.g., postgresql://user:pass@host:5432/dbname?sslmode=require)
DATABASE_ADMIN_URL No Direct (non-pooled) Postgres connection with elevated perms. Only used by scripts/migrate.js for DDL — falls back to DATABASE_URL if unset.
NEON_AUTH_BASE_URL Yes Neon Auth (Better Auth) base URL
NEON_AUTH_COOKIE_SECRET Yes Neon Auth session cookie signing secret (min 32 chars)
NEXT_PUBLIC_SITE_URL Yes Public site URL used for auth redirects and Origin header

Payments

Variable Required Description
STRIPE_SECRET_KEY Yes (if using Stripe) Stripe secret key (sk_live_... or sk_test_...)
STRIPE_WEBHOOK_SECRET Yes (if using Stripe webhooks) Stripe webhook signing secret (whsec_...) — from Stripe CLI or dashboard
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY Yes (if using Stripe) Stripe publishable key (pk_live_... or pk_test_...)

Stripe Price IDs (create in Stripe Dashboard → Products → add Price):

Variable Product
STRIPE_PRICE_STARTER Recurring $49/mo — Starter plan
STRIPE_PRICE_FARM Recurring $149/mo — Farm plan
STRIPE_PRICE_ENTERPRISE Recurring $399/mo — Enterprise plan
STRIPE_PRICE_HARVEST_REACH Recurring $79/mo — Harvest Reach add-on
STRIPE_PRICE_WHOLESALE_PORTAL Recurring $99/mo — Wholesale Portal add-on
STRIPE_PRICE_WATER_LOG Recurring $39/mo — Water Log add-on
STRIPE_PRICE_AI_TOOLS Recurring $59/mo — AI Intelligence add-on
STRIPE_PRICE_SQUARE_SYNC Recurring $39/mo — Square Sync add-on
STRIPE_PRICE_SMS_CAMPAIGNS Recurring $29/mo — SMS Campaigns add-on

For annual pricing, create separate annual prices (e.g., $441/yr for Starter = $49×12×0.75) and set STRIPE_PRICE_STARTER_ANNUAL, STRIPE_PRICE_FARM_ANNUAL, STRIPE_PRICE_ENTERPRISE_ANNUAL.

Variable Product
STRIPE_PRICE_STARTER_ANNUAL Annual Starter ($441/yr)
STRIPE_PRICE_FARM_ANNUAL Annual Farm ($1,341/yr)
STRIPE_PRICE_ENTERPRISE_ANNUAL Annual Enterprise ($3,591/yr)
STRIPE_PRICE_HARVEST_REACH_ANNUAL Annual Harvest Reach ($711/yr)
STRIPE_PRICE_WHOLESALE_PORTAL_ANNUAL Annual Wholesale Portal ($891/yr)
STRIPE_PRICE_WATER_LOG_ANNUAL Annual Water Log ($351/yr)
STRIPE_PRICE_AI_TOOLS_ANNUAL Annual AI Intelligence ($531/yr)
STRIPE_PRICE_SQUARE_SYNC_ANNUAL Annual Square Sync ($351/yr)
STRIPE_PRICE_SMS_CAMPAIGNS_ANNUAL Annual SMS Campaigns ($261/yr)
Variable Required Description
SQUARE_ACCESS_TOKEN Yes (if using Square) Square access token from Square Developer Dashboard
SQUARE_APPLICATION_ID Yes (if using Square) Square application ID
SQUARE_LOCATION_ID Yes (if using Square) Square location ID for inventory sync
SQUARE_WEBHOOK_SIGNATURE_KEY Yes (if using Square webhooks) Square webhook signature key

Email (Resend)

Variable Required Description
RESEND_API_KEY Yes (if using email) Resend API key (re_...)
RESEND_WEBHOOK_SECRET Yes (if using Resend webhooks) Resend webhook signing secret

Shipping (FedEx)

Variable Required Description
FEDEX_API_KEY Yes (if using FedEx) FedEx API key from FedEx Developer Portal
FEDEX_API_SECRET Yes (if using FedEx) FedEx API secret
FEDEX_ACCOUNT_NUMBER Yes (if using FedEx) FedEx account number
FEDEX_METER_NUMBER Yes (if using FedEx) FedEx meter number

AI

| Variable | Required | Description | |---|---| | OPENAI_API_KEY | Recommended | OpenAI API key (sk-...) — default AI provider for AI Intelligence Pack add-on |

AI Provider Setup (/admin/settings/integrations):

Route Commerce supports multiple AI providers via the Integrations settings page:

Provider Auth Method
OpenAI API key (OPENAI_API_KEY env var or per-brand settings)
Anthropic API key (set via /admin/settings/integrations AI Provider panel)
Google Gemini API key
xAI (Grok) API key
Custom / OAI-compatible API key + base URL

All AI tools (Campaign Writer, Pricing Advisor, Demand Forecasting, Route Optimizer, Stop Blast Advisor) respect the provider chosen in /admin/settings/integrations. The getAIClient() function in src/actions/integrations/ai-providers.ts handles provider instantiation dynamically.

Feature Flags (Optional)

Variable Required Description
NEXT_PUBLIC_ENABLED_ADDONS No Comma-separated list of add-on keys enabled for all brands (e.g., harvest_reach,wholesale_portal). Overrides per-brand brand_features table for white-label/self-hosted deployments.

3. Webhook Setup

Stripe

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Add endpoint: https://yourdomain.com/api/stripe/webhook
  3. Select events:
    • checkout.session.completed — saves subscription, triggers feature sync
    • customer.subscription.updated — syncs plan tier + add-on features on changes
    • customer.subscription.deleted — clears subscription, disables all add-ons
    • invoice.payment_succeeded — updates next billing date
    • invoice.payment_failed — marks subscription past_due, sends admin notification
  4. Copy the Webhook Secret (whsec_...) to STRIPE_WEBHOOK_SECRET

Resend

  1. Go to Resend Dashboard → Webhooks
  2. Add endpoint: https://yourdomain.com/api/resend/webhook
  3. Select events: email.delivered, email.open, email.click, email.bounce, email.complaint
  4. Copy the webhook secret to RESEND_WEBHOOK_SECRET

Square (optional)

  1. Go to Square Developer Dashboard → Webhooks
  2. Add subscription: https://yourdomain.com/api/square/webhook
  3. Select events: inventory.count.updated, order.created, payment.completed
  4. Copy the signature key to SQUARE_WEBHOOK_SIGNATURE_KEY

FedEx (optional)

  1. FedEx does not use outbound webhooks — track shipments via FedEx Track API polling or webhook subscription via FedEx Freight/LTL services

4. Vercel Cron Jobs

For scheduled tasks (stop reminders, campaign sends), add to vercel.json or configure in Vercel Dashboard → Project → Cron Jobs:

{
  "cron": [
    {
      "path": "/api/cron/send-stop-reminders",
      "schedule": "0 8 * * *"
    },
    {
      "path": "/api/cron/process-water-log-reminders",
      "schedule": "0 6 * * *"
    }
  ]
}

Note: Stop reminder emails are fired by Vercel cron jobs (/api/cron/send-stop-reminders), not by a database trigger. Verify the cron is configured in vercel.json (or Vercel Dashboard → Project → Cron Jobs).


5. Feature Flag Enablement (Post-Deploy)

After deployment, enable add-ons per brand via the admin UI or direct Postgres:

-- Enable Harvest Reach for a brand
SELECT set_brand_feature('your-brand-id', 'harvest_reach', true);

-- Enable Wholesale Portal
SELECT set_brand_feature('your-brand-id', 'wholesale_portal', true);

-- Enable Water Log (Tuxedo example)
SELECT set_brand_feature('64294306-5f42-463d-a5e8-2ad6c81a96de', 'water_log', true);

-- Enable AI Intelligence
SELECT set_brand_feature('your-brand-id', 'ai_tools', true);

-- Enable Square Sync
SELECT set_brand_feature('your-brand-id', 'square_sync', true);

Or use the admin UI at /admin/settings/apps (after logging in as platform_admin or brand_admin with settings access).


6. Post-Deploy Verification Checklist

Run through these after a successful deploy:

Core Platform

  • /admin loads with correct nav and section cards
  • Can create/edit/archive a stop in /admin/stops
  • Can create/edit a product in /admin/products
  • Cart works — add item, view cart, checkout
  • Payment completes successfully (test with Stripe test mode)

Public Storefronts

  • /pricing — public pricing page loads, monthly/annual toggle works, comparison table shows/hides
  • /tuxedo loads without platform SiteHeader/SiteFooter (brand header/footer only)
  • /indian-river-direct loads without platform SiteHeader/SiteFooter
  • "Admin" link visible in brand footer when logged in as admin
  • Stops display: tabbed view works, "This Week" default, "Full Season" pagination works
  • Schedule PDF download works (/api/tuxedo/schedule-pdf, /api/indian-river-direct/schedule-pdf)
  • Wholesale portal banner shows/hides based on wholesale_enabled setting
  • Zip code search works on IRD homepage

Add-ons

  • /admin/communications loads (Harvest Reach — requires enablement)
  • /admin/wholesale loads (Wholesale Portal — requires enablement)
  • /admin/settings/ai loads (AI Tools — requires enablement + API key)
  • /admin/settings/square-sync loads (Square Sync — requires enablement + Square credentials)
  • /admin/water-log loads for authorized brand (Water Log — requires enablement)

Admin Settings

  • /admin/settings — all sections visible including "Add-ons"
  • /admin/settings/brand — can save hero tagline, about headline, feature toggles
  • /admin/settings/apps — can enable/disable add-on features
  • /admin/settings/billing — shows plan tier, subscription status badge, next billing date, Stripe portal button
  • /admin/settings/integrations — AI provider configuration (OpenAI/Anthropic/Google/xAI/custom)
  • After toggling an add-on: dashboard card updates badge from "Add-on" to "Enabled"

Billing

  • Stripe webhook receives checkout.session.completed and saves subscription
  • /admin/settings/billing shows correct plan tier and add-on status
  • Subscription status badge shows active / past_due / canceled
  • Next billing date displays correctly (from stripe_current_period_end)
  • "Manage in Stripe Portal" button opens Stripe Customer Portal
  • Stripe Customer Portal shows correct plan + add-ons
  • Invoice history table shows mock invoice rows (ready for real Stripe data)
  • Monthly/annual toggle recalculates displayed prices on billing page
  • Add-on enable/disable triggers Stripe checkout / cancellation

Auth & Permissions

  • Login with platform admin — full access to all brands
  • Login with brand admin — access limited to assigned brand
  • Login with store employee — restricted to pickup and wholesale only
  • dev_session=platform_admin cookie works for local development bypass

Reports

  • /admin/reports — all tabs load (Overview, Orders by Stop, Sales by Product, Fulfillment, Pickup Status, Contact Growth, Campaigns)
  • Date range selector works
  • CSV export works for each report

7. Rollback Plan

Immediate Rollback (Vercel)

# Rollback to previous deployment in Vercel Dashboard
# Project → Deployments → find last good deployment → "Promote to Production"

Vercel keeps 10 recent deployments. For critical regressions, promote the previous deployment.

Database Rollback

# Revert last migration via Postgres
# (Restore from a `pg_dump` snapshot taken before the migration was applied)

# Or manually revert (example for migration 090)
psql $DATABASE_URL -c "ALTER TABLE brand_settings DROP COLUMN IF EXISTS schedule_pdf_notes;"

Important: Always restore from a known-good backup. In production, manually revert individual migrations to avoid data loss.

Environment Variable Rollback

Environment variables are version-controlled in Vercel. Restore a previous version in Vercel Dashboard → Project → Environment Variables → History.


8. Known Limitations & Future Work

Known Issues

  • dev_session auth: The dev_session cookie bypasses real Neon Auth (Better Auth). In production, ensure no dev_session cookie exists for regular users. The auth bypass only works when NODE_ENV !== "production".
  • Water Log visible to all brand admins: Currently Tuxedo-only by brand ID check. If another brand admin needs access, the check needs to be made configurable.
  • Communications tables (no RLS): communication_campaigns, communication_templates, communication_contacts, communication_message_logs have no row-level policies. All data access is through SECURITY DEFINER RPCs. Do not expose these tables via direct SQL from client-side code.
  • event_id not populated in message logs: send_campaign and send_stop_blast do not set event_id. The Resend webhook looks up logs by customer_email + subject instead. This is a known limitation — if Resend events are delayed, the lookup may fail to match.
  • Square sync is one-directional: Products import from Square → Route Commerce, but product edits in Route Commerce do not push back to Square.
  • SMS opt-in defaults to FALSE: communication_contacts.sms_opt_in defaults to false. Users must explicitly opt in for SMS campaigns.
  • brand_id: null for platform admins: getAdminUser() returns brand_id: null for platform_admin role. Always pass explicit brandId to server action functions.
  • Square webhook signature mismatch: The Square webhook handler uses crypto.createHmac("sha256", signatureKey) but Square sends the signature as base64-encoded. Verify signature encoding matches Square's documentation before enabling Square webhooks in production.
  • Stripe webhook reliability: Stripe webhook delivery is not guaranteed to be in-order or immediate. The webhook handler is idempotent — it can safely receive duplicate events. Consider implementing a webhook event log table to track processed event IDs for deduplication.
  • NO_RLS communications tables: All communications data is accessible via SECURITY DEFINER RPCs only — never expose direct SQL to these tables in client-side code.

Future Work (Non-Blocking)

  • Cursor-based pagination: Replace offset-based pagination (page * limit) with cursor-based pagination for large datasets (orders, contacts)
  • Webhook retry queue: Failed webhooks (Resend, Stripe) are not currently retried — implement a dead-letter queue or retry mechanism
  • Import Center: AI-powered import UI (/admin/import) was designed but not fully implemented
  • Multi-brand support in public storefront: Currently hardcoded for Tuxedo and IRD — a generic /[brandSlug] route would support all brands
  • AI features in reports: ReportsDashboard has an "Explain" button for AI-powered explanations but it requires OpenAI key + working API proxy
  • Square inventory mapping: syncInventoryToSquare and syncInventoryFromSquare require a square_catalog_object_id column on products to map RC products to Square catalog items — not yet implemented
  • Webhook event deduplication: Add a stripe_events_processed table or similar to track processed Stripe event IDs and prevent duplicate processing

9. Contacts & On-Call

Role Contact
Platform Admin First Neon Auth user becomes the platform admin via provision-admin.ts
Kyle Martinez Primary contact — Route Commerce owner
Neon Support neon.tech/support
Vercel Support vercel.com/support

For Vercel production incidents: vercel.com/deployments → find deployment → "Support" button.