# Enterprise staging release setup

This release must be installed as a second application at `staging.beatmycarquote.com`. Do not extract it into `/home/feszouuvxj/beatmycarquote` and do not change the working live application.

## 1. Apply the staging-release migration

The staging database already has migrations `001` and `002`. In the Supabase staging project, open **SQL Editor**, create a query named `003 staging release`, paste the complete contents of:

`database/migrations/003_staging_release.sql`

Run it once. It adds the typed worker queue, abandoned-job recovery, and lossless legacy JSON fields. A successful result says `Success. No rows returned`.

For the colour-flexibility release, also run `database/migrations/008_exterior_colour_flexibility.sql` in the same staging database before restarting the application. It records the customer's choice and treats a blank exterior colour as flexible. A successful result says `Success. No rows returned`.

For the dealer activity release, run `database/migrations/009_dealer_quote_outcomes.sql` after migration 008. It stores the optional visit and purchase confirmations attached to submitted dealer quotes.

For the dealer deadline release, run `database/migrations/010_dealer_business_day_deadlines.sql` after migration 009. It gives every dealership until 10:00 p.m. Toronto time on the next Ontario business day and keeps unanswered expired Bronze/Silver assignments counted against the monthly allowance.

## 2. Create Stripe test subscriptions

Remain in Stripe **test mode**.

Create three monthly recurring CAD prices:

| Product | Price | Billing |
|---|---:|---|
| Beat My Car Quote Bronze | CAD $99.99 | Monthly |
| Beat My Car Quote Silver | CAD $199.99 | Monthly |
| Beat My Car Quote Gold | CAD $299.99 | Monthly |

Copy each Price ID beginning with `price_`. Configure the Stripe customer portal so a test dealership can update payment details and cancel a subscription.

Create a staging webhook endpoint:

`https://staging.beatmycarquote.com/api/stripe-webhook`

Subscribe it to:

- `checkout.session.completed`
- `checkout.session.async_payment_succeeded`
- `invoice.paid`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`

Copy its signing secret beginning with `whsec_`. The staging endpoint must use test-mode Stripe keys and test-mode price IDs only.

## 3. Create the staging Node application

In Spaceship cPanel, create a separate Node.js application:

- Node.js version: `22.23.2`
- Application mode: `Production`
- Application root: `beatmycarquote-staging`
- Application URL: `staging.beatmycarquote.com`
- Startup file: `server.js`

Upload and extract the staging ZIP directly inside `/home/feszouuvxj/beatmycarquote-staging`. Confirm `server.js`, `package.json`, `.next/BUILD_ID`, `app`, `lib`, and `public` are directly inside that folder.

Click **Run NPM Install** once because this release adds the `postgres` package. Do not run `npm build`; the ZIP contains a verified production build.

## 4. Configure staging environment variables

Keep every secret in Spaceship's **Setup Node.js App** environment-variable editor, never in a file under the application root.

```text
NODE_ENV=production
SITE_URL=https://staging.beatmycarquote.com
NEXT_PUBLIC_SITE_URL=https://staging.beatmycarquote.com

DATABASE_URL=the Supabase Session pooler URI on port 5432 with sslmode=require
DATABASE_POOL_SIZE=8

STRIPE_SECRET_KEY=sk_test_replace_me
STRIPE_WEBHOOK_SECRET=whsec_replace_me
STRIPE_RESEARCH_FEE_CENTS=5000
STRIPE_CURRENCY=cad
STRIPE_AUTOMATIC_TAX=false
STRIPE_BRONZE_PRICE_ID=price_replace_me
STRIPE_SILVER_PRICE_ID=price_replace_me
STRIPE_GOLD_PRICE_ID=price_replace_me
STRIPE_DEALER_BILLING_PORTAL_RETURN_URL=https://staging.beatmycarquote.com/dealer/dashboard

ADMIN_PASSWORD=use_a_staging_admin_password_with_at_least_12_characters
ADMIN_SESSION_SECRET=use_a_different_random_value_with_at_least_32_characters
TRACKER_DATA_FILE=/home/feszouuvxj/beatmycarquote-staging-private/requests.json
DEALER_DIRECTORY_FILE=/home/feszouuvxj/beatmycarquote-staging-private/dealer-directory.json

RESEND_API_KEY=re_replace_me
REPORT_FROM_EMAIL=Beat My Car Quote <reports@beatmycarquote.com>
REPORT_REPLY_TO=reports@beatmycarquote.com
DEALER_FROM_EMAIL=Beat My Car Quote Dealers <dealers@beatmycarquote.com>
DEALER_SESSION_TTL_HOURS=168
DEALER_LOGIN_LINK_TTL_MINUTES=15

WORKER_ID=spaceship-staging-worker
WORKER_BATCH_SIZE=25
WORKER_DATABASE_POOL_SIZE=4
```

The staging tracker paths are deliberately different from the live paths. Do not point staging at `/home/feszouuvxj/beatmycarquote-private`.

Save the variables and restart only the staging application.

## 5. Confirm the staging surfaces

1. Open `https://staging.beatmycarquote.com/admin` and sign in.
2. Open **Enterprise marketplace**.
3. Confirm the system status card says `Operations data is ready`.
4. Open `https://staging.beatmycarquote.com/dealer/join` and confirm all three plans appear.
5. Open `https://staging.beatmycarquote.com/dealer` and confirm the secure email sign-in page appears.

If database health fails, verify that `DATABASE_URL` is the Supabase **Session pooler** URI, uses port `5432`, has the database password substituted, and ends in `sslmode=require`.

## 6. Process automatic Gold matches

The admin staging workspace has a **Process matching queue** button for controlled tests.

For continuous production operation, run a separate worker service with:

```text
npm run worker:enterprise
```

For a one-cycle staging or cron test:

```text
npm run worker:enterprise -- --once
```

Spaceship shared hosting is suitable for staging validation, not the final 1,000-requests-per-hour production target. Production requires separately scalable web and worker processes.

## 7. Required staging test order

1. Submit a customer request using Stripe test payment.
2. Confirm it appears in **Enterprise marketplace** without deleting the legacy JSON record.
3. Enroll test Bronze, Silver, and Gold dealerships through Stripe test checkout.
4. Request a dealer sign-in link and verify the link expires after use.
5. Assign five opportunities to Bronze and ten to Silver; confirm the next assignment is rejected.
6. Upload the Gold CSV template and run matching.
7. Submit at least four current, confirmed offers from different dealerships.
8. Create a report review version and preview the exact PDF.
9. Confirm **Send approved report** remains disabled before approval.
10. Approve the exact version and send it to a test mailbox.
11. Confirm the customer request changes to `delivered` only after Resend accepts the email.
12. Sign in as a dealership owner or manager. Under **Account**, test **Update credit card** and **Cancel subscription** with a Stripe test subscription.
13. Confirm a scheduled cancellation remains usable through its paid period and the dashboard displays its ending date.
14. Complete a customer payment and dealer membership payment, then use **Export completed sales CSV** in the enterprise admin workspace and confirm both appear.

## 8. Stripe customer portal

In Stripe test mode, open **Settings > Billing > Customer portal** and enable both **Payment methods** and **Cancel subscription**. Set cancellation to **At end of billing period**, save the configuration, and repeat this setup separately in live mode before releasing to production.

## 9. Existing JSON migration

Do not migrate live records during initial staging testing. The migration tool defaults to dry-run mode:

```text
npm run migrate:enterprise:dry-run
```

It validates IDs and duplicate Stripe sessions, prints record counts, and writes nothing. The commit command exists but must not be used until backups, reconciliation rules, staging acceptance tests, and a rollback window are approved:

```text
npm run migrate:enterprise:commit
```

The migration preserves the complete legacy source record in `legacy_payload` and never modifies the JSON source files.
