17 KiB
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 001–092 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
- Go to Stripe Dashboard → Developers → Webhooks
- Add endpoint:
https://yourdomain.com/api/stripe/webhook - Select events:
checkout.session.completed— saves subscription, triggers feature synccustomer.subscription.updated— syncs plan tier + add-on features on changescustomer.subscription.deleted— clears subscription, disables all add-onsinvoice.payment_succeeded— updates next billing dateinvoice.payment_failed— marks subscriptionpast_due, sends admin notification
- Copy the Webhook Secret (
whsec_...) toSTRIPE_WEBHOOK_SECRET
Resend
- Go to Resend Dashboard → Webhooks
- Add endpoint:
https://yourdomain.com/api/resend/webhook - Select events:
email.delivered,email.open,email.click,email.bounce,email.complaint - Copy the webhook secret to
RESEND_WEBHOOK_SECRET
Square (optional)
- Go to Square Developer Dashboard → Webhooks
- Add subscription:
https://yourdomain.com/api/square/webhook - Select events:
inventory.count.updated,order.created,payment.completed - Copy the signature key to
SQUARE_WEBHOOK_SIGNATURE_KEY
FedEx (optional)
- 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
/adminloads 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/tuxedoloads without platform SiteHeader/SiteFooter (brand header/footer only)/indian-river-directloads 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_enabledsetting - Zip code search works on IRD homepage
Add-ons
/admin/communicationsloads (Harvest Reach — requires enablement)/admin/wholesaleloads (Wholesale Portal — requires enablement)/admin/settings/ailoads (AI Tools — requires enablement + API key)/admin/settings/square-syncloads (Square Sync — requires enablement + Square credentials)/admin/water-logloads 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.completedand saves subscription /admin/settings/billingshows 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_admincookie 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 reseton 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_sessionauth: Thedev_sessioncookie bypasses real Supabase auth. In production, ensure nodev_sessioncookie exists for regular users. The auth bypass only works whenNODE_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_logshave RLS disabled. All data access is through SECURITY DEFINER RPCs. Do not expose these tables via direct Supabase queries. event_idnot populated in message logs:send_campaignandsend_stop_blastdo not setevent_id. The Resend webhook looks up logs bycustomer_email + subjectinstead. 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_indefaults tofalse. Users must explicitly opt in for SMS campaigns. brand_id: nullfor platform admins:getAdminUser()returnsbrand_id: nullforplatform_adminrole. Always pass explicitbrandIdto 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_RLScommunications 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:
ReportsDashboardhas an "Explain" button for AI-powered explanations but it requires OpenAI key + working API proxy - Square inventory mapping:
syncInventoryToSquareandsyncInventoryFromSquarerequire asquare_catalog_object_idcolumn on products to map RC products to Square catalog items — not yet implemented - Webhook event deduplication: Add a
stripe_events_processedtable 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.