# BlackRed Raffle — PHP Backend (Phase 2)

Production-grade raw PHP 8.1+ backend. No framework. OWASP-aligned. Built on the dual-balance wallet model (BR-AML-001).

**Phase 2 ships the 3-step signup wizard, login, and `/api/me`.**

---

## What's in this release

### Phase 1 (foundation, already running)
- Front controller, router, middleware pipeline
- Error handling, structured JSON logging, security headers
- PDO database wrapper with deadlock-retry transactions
- `GET /api/health`
- Bundled `vendor/` (Dotenv, Monolog) — works without Composer

### Phase 2 (this release)
- **3-step signup wizard** matching the live `signUp.html`:
  - **Step 1** — `POST /api/auth/signup/lookup` — phone + network → ANM `AII` lookup → MoMo registered name
  - **Step 2** — `POST /api/auth/signup/verify` — phone + 4-char alphanumeric code → checks against the live USSD `authtoken` table
  - **Step 3** — `POST /api/auth/signup/complete` — phone + password → creates player atomically, **auto-login via session cookie**
- **`POST /api/auth/login`** — phone + password → session cookie
- **`POST /api/auth/logout`** — kills session
- **`GET /api/me`** — full player + dual balance with `playFormatted: "0.00"` and `currency: "GHS"`
- ANM Orchard client with HMAC-SHA256 signing, name-lookup caching (30 days), full request/response logging to `momoApiCallLog`
- Argon2id passwords (auto-rehash on cost upgrade)
- Server-side sessions, MySQL-backed, sliding expiry
- Per-IP rate limiting via atomic UPSERT (no Redis)
- Constant-time login (no phone-enumeration leak)
- Modal-based error UX in the frontend
- 54 unit tests + 11 integration tests, all passing

---

## How signup works

```
┌──────────┐                ┌──────────┐                ┌──────────┐
│ User UI  │                │ Backend  │                │   ANM    │
└────┬─────┘                └────┬─────┘                └────┬─────┘
     │                           │                           │
     │ Phone+Network             │                           │
     │  →                        │                           │
     │                           │  AII lookup               │
     │                           │     →                     │
     │                           │  ←  Registered name       │
     │  ←  Show name beneath     │                           │
     │      input field          │                           │
     │                           │                           │
   User dials *920*995#                                      │
   on their phone, gets                                      │
   4-char code on screen                                     │
                                                             │
     │ Phone + 4-char code       │                           │
     │  →                        │                           │
     │                           │  Hash matches authtoken?  │
     │                           │  (set by USSD endpoint)   │
     │                           │                           │
     │  ←  ok                    │                           │
     │                           │                           │
     │ Phone + password          │                           │
     │  →                        │                           │
     │                           │  Create player +          │
     │                           │  accounts + wallets       │
     │                           │  (atomic stored proc)     │
     │  ←  ok + Set-Cookie       │                           │
     │      BR_SESSION=...       │                           │
     │                           │                           │
   User auto-logged in, lands on dashboard.html
```

---

## Deployment to `pncgw.engboxx.com` (development)

This is a drop-in upgrade of the Phase 1 deployment. Keep your existing `.env` and add a few new keys.

### Pre-flight: rotate ANM credentials

The credentials you shared in chat are now considered exposed. Before deploying, log in to the ANM dashboard and **regenerate `client_key` and `secret_key`**. You'll paste the new ones into `.env` in step 4.

### Step 1 — Apply migration 002

In cPanel:

1. Open **phpMyAdmin** → select database **`amoamvfc_br_mainDB`**
2. Click the **Import** tab
3. Choose file → upload `migrations/002_phase2_auth.sql`
4. Click **Go**

You should see "Import has been successfully finished". The migration creates:
- `rateLimitBucket`
- `csrfToken`
- `signupSession`
- Drops legacy `signupOtp` if present (was unused)
- Stored procedure `createPlayerWithWallet`

**Do NOT run** `migrations/test_only_authtoken.sql` — your live USSD endpoint already created that table.

### Step 2 — Backup the existing app

In cPanel **File Manager**:

1. Navigate to `/home/amoamvfc/pncgw.engboxx.com/`
2. Right-click `blackred/` → **Compress** → save as `blackred-backup-phase1.zip`
3. Download it as backup. If anything breaks, you can restore.

### Step 3 — Upload the new code

1. Upload `blackred-app-phase2.zip` to `/home/amoamvfc/pncgw.engboxx.com/`
2. Right-click → **Extract**
3. The extracted folder will be `blackred-app/`. The new code is inside.
4. Now copy these directories from `blackred-app/` into `blackred/`, **overwriting**:
   - `src/`
   - `vendor/`
   - `tests/`
   - `migrations/` (just the new SQL files; keep your existing one)
   - `public/index.php`
   - `public/app/signUp.html` ← this has the wired-up frontend

   **Do NOT overwrite:**
   - `.env` (keep your live config)
   - `logs/` (don't lose logs)
   - The per-directory `.htaccess` files in `src/`, `vendor/`, `migrations/`, `logs/`

5. After moving files, delete `blackred-app/` and the zip.

### Step 4 — Update `.env`

Edit `/home/amoamvfc/pncgw.engboxx.com/blackred/.env` (right-click in File Manager → Edit). **Add these new keys** at the bottom (or anywhere):

```ini
# AppsNMobile (ANM) Orchard — MoMo lookups, deposits, withdrawals
ANM_BASE_URL=https://orchard-api.anmgw.com
ANM_CLIENT_KEY=<your_rotated_key_here>
ANM_SECRET_KEY=<your_rotated_secret_here>
ANM_SERVICE_ID=70
ANM_REQUEST_TIMEOUT_SECONDS=30

# Rate limit for the lookup endpoint (it costs money per call to ANM)
RATE_LIMIT_SIGNUP_LOOKUP_PER_MIN=5
```

Save.

### Step 5 — Verify it boots

Visit:
```
https://pncgw.engboxx.com/blackred/public/api/health
```

Expected: `{"ok":true,"app":"BlackRed Raffle","env":"production","db":true,"time":"..."}`.

If you see anything else, check `/home/amoamvfc/pncgw.engboxx.com/blackred/logs/app.log` for the error.

### Step 6 — Run a real end-to-end signup

1. **Visit** `https://pncgw.engboxx.com/blackred/public/app/signUp.html`
2. **Step 1** — enter your real Ghana phone, pick the matching network. The MoMo name should appear beneath the input within ~2 seconds (this is a real ANM call).
3. **Step 2** — minimize the browser, **dial `*920*995#`** on your phone. You'll get a 4-character code on screen.
4. Return to the browser, **type the 4 characters** into the boxes. Click Proceed. The boxes turn green.
5. **Step 3** — pick a password (8+ chars, uppercase, number, symbol like `Password1!`). Click Create.
6. You should see "You're in" success screen. Click **Enter BlackRed** — lands on `dashboard.html`.

If anything errors, you'll see a modal with the reason.

### Step 7 — Verify the session

After step 6, in the same browser session, visit:
```
https://pncgw.engboxx.com/blackred/public/api/me
```

You should see the full player JSON with `"playFormatted": "0.00"`.

### Step 8 — Verify rollback works

Visit:
```
https://pncgw.engboxx.com/blackred/public/api/auth/logout
```
(Easier — open browser devtools → Network → manually POST. Or use `curl`.)

Then re-visit `/api/me` — should now return 401.

---

## Troubleshooting

### "Could not find a Mobile Money account on this number"

The number isn't registered with the network the user picked, OR the bank code mapping doesn't match what ANM uses.

This is the **expected** outcome when a user picks the wrong network for their number — we don't enforce a strict prefix-to-network mapping client-side because number portability and prefix reassignments make that unreliable. ANM is the source of truth.

What the user should do: go back to step 1, pick the correct network, retry.

Bank code translation in our code:
- `MTN` (UI: "MTN MoMo") → ANM `bank_code` = `MTN`
- `ATL` (UI: "AirtelTigo") → ANM `bank_code` = `AIR`
- `TEL` (UI: "Telecel") → ANM `bank_code` = `VOD`

If a real ATL or TEL number gets rejected even with the correct network, ANM may want different codes. Edit `src/Integrations/AnmClient.php`, method `bankCodeFor`.

### "Code is incorrect or has expired"

- The `*920*995#` code is valid for 5 minutes
- The user dialed before, then walked away — the code expired
- The user copy-pasted from a screenshot of an OLD code
- The hashing differs — check that your USSD endpoint generates 4-char codes from the same alphabet (`0-9 A-Z` minus `O I`) and SHA-512 hashes them. Our verifier matches that exact format.

### Modal flashes briefly then disappears

The user clicked outside the modal (overlay). That dismisses it. The error is also in the browser console.

### Signup goes through but I don't see new tables in phpMyAdmin

You skipped Step 1. Apply `002_phase2_auth.sql` via phpMyAdmin Import.

---

## Endpoint reference

### `GET /api/health`
Public. Returns `{ok, app, env, db, time}`.

### `POST /api/auth/signup/lookup`
Public. Rate-limited 5/min/IP. Body: `{phone, network}` (network = `"mtn"|"at"|"telecel"`).

Returns: `{ok, phone, phoneFormatted, provider, registeredName}` or 4xx with `{error, message}`.

Error codes:
- `phone_already_registered` — try login instead
- `not_found` — ANM doesn't recognise the number on the chosen network (try a different network)
- `network_error` — couldn't reach ANM

### `POST /api/auth/signup/verify`
Public. Rate-limited 3/min/IP. Body: `{phone, code}` (code = 4 alphanumeric chars).

Returns: `{ok: true}` or 4xx.

Error codes:
- `invalid_or_expired_code`
- `signup_session_missing` — step 1 not done or expired
- `lookup_required_first`

### `POST /api/auth/signup/complete`
Public. Rate-limited 3/min/IP. Body: `{phone, password}`.

Password rules: 8+ chars, at least one uppercase, one digit, one symbol.

Returns: `{ok, playerId, phone, expiresAt}` with `Set-Cookie: BR_SESSION=...` (auto-login).

### `POST /api/auth/login`
Public. Rate-limited 5/min/IP. Body: `{phone, password}`. Returns session cookie.

### `POST /api/auth/logout`
Authenticated. Clears the cookie. Idempotent.

### `GET /api/me`
Authenticated. Returns full player + dual-balance:

```json
{
  "player": {
    "id": 1,
    "phone": "233244000001",
    "phoneFormatted": "+233 24 400 0001",
    "paymentProvider": "MTN",
    "registeredName": "KWAME TEST USER",
    ...
  },
  "balance": {
    "playPesewas": 0,
    "payoutPesewas": 0,
    "totalPesewas": 0,
    "playFormatted": "0.00",
    "payoutFormatted": "0.00",
    "totalFormatted": "0.00",
    "currency": "GHS",
    "fiftyPercentRuleApplied": true,
    ...
  }
}
```

---

## Tests

```bash
php tests/run.php
```

Should output `Tests: 54 | Passed: 54`.

For the integration test (requires MySQL + the test_only_authtoken migration), see the script in the project root.

---

## Security model summary

- **Sessions** server-side (table `playerSession`); cookies are opaque, `HttpOnly`, `Secure` in prod, `SameSite=Strict`.
- **Passwords** Argon2id with cost params from `.env`, auto-rehash on cost upgrade.
- **OTPs** are SHA-512 (matching the live USSD). **Hardening item:** switch both endpoints to Argon2id eventually — SHA-512 of a 4-char code is brute-forceable if the table is dumped.
- **Rate limiting** per-IP, per-endpoint, atomic UPSERT counters.
- **Login** constant-time on failure; same response shape for "wrong password" and "no such phone".
- **Signup** server-enforced state machine — step 3 cannot be called without steps 1 + 2 in the same session.
- **Phone enumeration** prevented at all endpoints.
- **ANM** all calls signed with HMAC-SHA256, full audit log in `momoApiCallLog`.

---

## What's next

- **Phase 3** — Wallets & deposits: ANM CTM (Customer-to-Merchant), deposit flow with provider callback handler, ledger postings, the 50% withdrawal rule enforced at request time
- **Phase 4** — The game itself: stake endpoint, RNG draw engine, win resolution, results
- **Phase 5** — CMS, admin & ops dashboards
- **Phase 6** — Hardening, load tests, pen test
