# Vendor Onboarding API (4-step seller application)

Same flow as NumSport. Each step saves on its own, so a vendor can close the app and
resume later. `vendors.onboarding_step` (0–4) records progress, and `status` stays
`'draft'` until step 4. Drafts do not appear in the admin's default vendor list.

The old `POST /api/vendors/register` still works unchanged.

All routes are under `/api/vendors` and need the `X-API-Key` header.

## Deploy

```bash
node src/scripts/add-vendor-onboarding.js   # run BEFORE deploying the code (idempotent)
```

Existing vendors are marked `onboarding_step = 4`; they are not sent back through the wizard.

Optional env:

| Var | Purpose |
|---|---|
| `GSTIN_VERIFY_URL` | Live GSTIN lookup provider, `{gstin}` placeholder. Without it only the offline format + check-digit check runs. |
| `GSTIN_VERIFY_KEY` | Bearer token for that provider |
| `SELLER_TERMS_VERSION` | Terms version stored when the app does not send `terms_version` (default `2026-09-v1`) |

## Endpoints

```
GET  /apply/:user_id           progress + everything entered (step_1..step_4, terms)
GET  /apply/categories         business-category dropdown (vendor_categories)

POST /apply/step-1             multipart: logo
     user_id, store_name, business_category_id, support_email, support_phone,
     store_description?, owner_name?

POST /apply/verify-gstin       { gstin, gst_type }  -- [Verify] button, saves nothing
POST /apply/step-2             multipart: gst_certificate, pan_card
     user_id, gst_type (regular_gstin | enrolment_id), gstin_or_enrolment_id,
     legal_name, pan_number?, registered_address, pincode?

GET  /apply/ifsc/:code         [Check] button -> { ifsc, bank_name, branch }
POST /apply/step-3             user_id, account_holder, account_number,
                               confirm_account_number, ifsc

POST /apply/step-4             user_id, address_line1, address_line2?, city, state,
                               pincode, contact_person, contact_phone, warehouse_name?,
                               terms_accepted (or accept_terms) = true, terms_version?

POST /apply/retry-route        { user_id }  -- re-run Razorpay Route linking
```

Errors the vendor can fix return `400 { success: false, message }`.

## Rules

- **Steps cannot be skipped**, but any step already reached can be edited again. Re-saving an earlier step does not lower `onboarding_step`.
- **Step 1:** the store slug is created once and never changes. The support email must be unique across stores.
- **Step 2:** the GSTIN check digit (modulus 36) is checked offline. The state inside the GSTIN wins over any state typed on the form. A PAN that contradicts the GSTIN is refused. `is_verified` is set only by a successful live lookup, never from what the client posts. Step 2 also writes `vendors.gst_number`.
- **Step 3:** the account is saved in `vendor_payout_accounts`, the same row RazorpayX withdrawals read, so onboarding also sets up payouts. Responses only ever show `xxxx 1234`.
  - Razorpay Route runs **after** the save, so a Route failure never loses the account.
  - The `route` field in the response shows `linked` / `activation_status`, or the `reason` it failed.
  - `requested` is the normal status; Razorpay activates the account after its own checks.
- **Step 4:** saves the primary warehouse. Re-submitting replaces it, and there is only ever one primary. The address is also copied to `vendors.address/city/state/pincode`, which delivery pricing uses as the origin.
  - Status changes `draft`/`rejected` → `pending`, and admins get a `vendor_request` notification only on that change.
  - Approval is still done with `POST /admin/:vendor_id/status`.

## Not built

- **Route transfers:** money still reaches vendors through the wallet and RazorpayX withdrawal. The Route linked account is only opened and kept ready.
- **Route webhooks:** the `account.activated` webhook is not handled. `razorpay_route_status` changes only when step 3 or retry-route runs.
