Files
route-commerce/PRODUCTION_DEPLOYMENT_CHECKLIST.md
Nora 0ac4beaaa8 fix: react-doctor errors → 0 errors, 1649 warnings (46/100)
- Add requireAuth() to admin-permissions.ts as recognized auth call
- Convert getAdminUser() → requireAuth() across 73 admin action files
- Add getSession() to public/wholesale server actions
- Fix multi-line return type corruption from earlier auto-fixers
- Move FedEx token cache to non-'use server' module
- Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD,
  EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS
- Update Stripe API version 2026-05-27 → 2026-06-24
- Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient
- Fix 51 TypeScript errors (return type corruption, missing imports)
2026-06-25 23:49:37 -06:00

17 KiB
Raw Permalink Blame History

Route Commerce — Production Deployment Checklist

Platform Version: 1.6 Last Updated: 2026-05-13 Environment: Next.js 16 (App Router) · Supabase · Stripe · Square · Resend · FedEx


1. Migration Order

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

# Push all migrations (sequential)
npm run migrate:one 001
npm run migrate:one 002
# ... through ...
npm run migrate:one 092

# Or push all at once via Supabase CLI
supabase db push

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
NEXT_PUBLIC_SUPABASE_URL Yes Supabase project URL (e.g., https://xxx.supabase.co)
NEXT_PUBLIC_SUPABASE_ANON_KEY Yes Supabase anon/public key (safe to expose in browser)
SUPABASE_SERVICE_ROLE_KEY Yes Supabase service role key — never expose in browser, server-side only
SUPABASE_PAT Yes Supabase Personal Access Token for migrations CLI

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 triggered by a Supabase time-based trigger (send_stop_reminder_email). Verify the trigger is active:

SELECT * FROM pg_publication_tables WHERE tablename = 'stops';

If not, recreate: ALTER PUBLICATION supabase_realtime PUBLISH INSERT, UPDATE ON TABLE stops;


5. Feature Flag Enablement (Post-Deploy)

After deployment, enable add-ons per brand via the admin UI or Supabase SQL:

-- 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 Supabase
supabase db reset --backup-id <backup-before-migration>

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

Important: Only use supabase db reset on staging. 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 Supabase 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 RLS disabled. All data access is through SECURITY DEFINER RPCs. Do not expose these tables via direct Supabase queries.
  • 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 Supabase queries 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 Set via Supabase Auth dashboard
Kyle Martinez Primary contact — Route Commerce owner
Supabase Support supabase.com/support
Vercel Support vercel.com/support

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

For Supabase production incidents: supabase.com/dashboard → project → "Support" tab.