# Ride PIN (start verification)

## Purpose

A 4-digit Ride PIN confirms the correct customer and driver are together before the trip starts. It is **not** an auth OTP (no login, password reset, or phone verification).

## Lifecycle

```text
Ride assigned (direct accept OR offer accept)
  → backend generates PIN + stores bcrypt hash
  → customer sees ridePin
  → DRIVER_ARRIVING / DRIVER_ARRIVED (existing pickup GPS rules)
  → driver POST /rides/:rideId/verify-pin
  → DRIVER_ARRIVED → IN_PROGRESS
  → ride.updated (STATUS_CHANGED)
```

PIN is generated once in the shared `createAcceptedRideInTransaction` path so direct accept and offer accept behave the same.

## Storage & security

| Field | Storage | Exposed? |
|-------|---------|----------|
| `ridePinHash` | bcrypt hash in `rides` | Never |
| plaintext PIN | Redis only (`ride:pin:{rideId}`, TTL 24h) | Customer only |
| `ridePinVerifiedAt` | DB | via `ridePinVerified` |
| `ridePinAttempts` / `ridePinLockedUntil` | DB | Never |

Never compare plaintext to DB. Never put `ridePin` or `ridePinHash` in driver API/socket payloads.

## Who can see / verify

- **Customer** (owner): `ridePin` on ride detail/list and customer `ride.updated` when Redis still has the value.
- **Assigned driver**: `ridePinVerified` only; submits PIN via verify endpoint.
- Unrelated users: denied by existing ownership checks.

## Verify endpoint

`POST /rides/:rideId/verify-pin`

```json
{ "pin": "4827" }
```

Rules:

- JWT + DRIVER role
- `ride.driverId` must match authenticated driver
- Status must be `DRIVER_ARRIVED` (not ACCEPTED / ARRIVING / IN_PROGRESS / COMPLETED / CANCELLED)
- PIN: required string, exactly 4 digits (`^\d{4}$`)
- Success → `IN_PROGRESS`, set `startedAt` + `ridePinVerifiedAt`, emit `ride.updated`
- Wrong PIN → 400 `Invalid ride PIN`, increment attempts, **no** lifecycle socket
- Already verified (`IN_PROGRESS` + `ridePinVerifiedAt`) → 200 idempotent, no duplicate emit

## Attempts & lock

Env (defaults):

- `RIDE_PIN_MAX_ATTEMPTS=5`
- `RIDE_PIN_LOCK_MINUTES=10`

After max failures: set `ridePinLockedUntil`. While locked → `Ride PIN verification temporarily locked`. After expiry, attempts reset and verification is allowed again.

## GPS relationship

PIN does **not** replace GPS. Pickup proximity still gates arriving/arrived. Destination proximity still gates completion. Verify-pin only replaces `PATCH .../status` for starting the trip (`IN_PROGRESS`).

## Socket

Continue using only `ride.updated`. No `ride.pin_verified` event.

## Status endpoint

`PATCH /rides/:id/status` no longer accepts `IN_PROGRESS`. Drivers must use verify-pin after `DRIVER_ARRIVED`.
