# Norbiz Lotto — API Architecture & Development Structure

**Version:** 1.0  
**Stack:** Laravel 12 · PHP 8.4 · Sanctum · MySQL  
**Clients (Phase 1):** Admin (Vue) · Customer App (Flutter) · Customer Website (Next.js/Vue)  
**Future:** Vendor App (`role_id = 3` reserved in roles — not in Phase 1 scope)
**References:** [Norbiz scope](/Users/sigosoftpvtltd/Downloads/Norbiz%20scope.md), [Game dev doc](/Users/sigosoftpvtltd/Downloads/nobiz_dev_doc.pdf), [Admin design](https://ourworks.co.in/norbiz-lotto-design), [Mobile UI](https://www.figma.com/design/np30NgbXAlMMrVoS7Yveym/Norbiz-Lotto?node-id=0-1), [Website UI](https://www.figma.com/design/np30NgbXAlMMrVoS7Yveym/Norbiz-Lotto?node-id=86-904)  
**Architecture references:** `saimpex-backend`, `airotrack-backend`

---

## 1. Executive Summary

Norbiz Lotto backend is a **single Laravel API** serving three client surfaces in **Phase 1** (admin, customer app, customer website). It follows the multi-route-file pattern used in Sigosoft's existing projects:

- One `routes/api.php` entry point
- Separate route files per audience (`admin`, `customer`, `website` in Phase 1; `vendor` added in a future phase)
- Shared services for game logic, draws, wallets, and payments
- Consistent JSON response envelope via `ResponseSender`
- Role-based access via `users.role_id` + Sanctum tokens

**Phase 1 scope:** Admin login + customer (app/website) login and all customer-facing flows. **Vendor login, vendor APIs, and admin vendor management are out of scope for Phase 1** but `role_id = 3` (Vendor) remains in the `roles` table for future agent/POS support.

All draw results derive from **official US state Pick 3 + Pick 4** results. The platform does not generate random numbers.

---

## 2. High-Level Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         CLIENT APPLICATIONS                              │
├──────────────┬──────────────┬──────────────────┬────────────────────────┤
│ Admin (Vue)  │ Flutter App  │ Website (Next/Vue)│ Vendor App (future)   │
└──────┬───────┴──────┬───────┴────────┬─────────┴────────────────────────┘
       │              │                │
       └──────────────┴────────────────┘
                                   │
                          HTTPS  /api/*
                                   │
       ┌───────────────────────────▼───────────────────────────┐
       │              Laravel 12 API (this repo)                  │
       │  ┌─────────┐ ┌──────────┐ ┌────────┐ ┌─────────────────┐ │
       │  │ Routes  │ │Middleware│ │Controllers│ │ Form Requests │ │
       │  └────┬────┘ └────┬─────┘ └────┬───┘ └────────┬────────┘ │
       │       └───────────┴────────────┴──────────────┘          │
       │                          │                                 │
       │  ┌───────────────────────▼────────────────────────────┐  │
       │  │ Services: DrawEngine, TicketService, WalletService,   │  │
       │  │ PayoutCalculator, RiskControl, PaymentGatewayManager  │  │
       │  └───────────────────────┬────────────────────────────┘  │
       │                          │                                 │
       │  ┌──────────┬────────────┴──────────┬──────────┐           │
       │  │ Models   │   Repositories (opt)  │  Events  │           │
       │  └──────────┴───────────────────────┴──────────┘           │
       └───────────────────────────┬───────────────────────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              │                    │                    │
         MySQL DB            Redis/Cache           Queue Workers
              │                    │                    │
              │              ┌─────▼─────┐    ┌────────▼────────┐
              │              │  Sessions │    │ ProcessDrawJob  │
              │              │  OTP TTL  │    │ SendPushJob     │
              │              └───────────┘    │ WebhookJob      │
              │                               └─────────────────┘
              │
    External: MonCash, PayPal, Card gateways, SMS/OTP, FCM, US Lottery APIs
```

---

## 3. Project Directory Structure

Aligned with `saimpex-backend` and `airotrack-backend` conventions:

```
app/
├── Http/
│   ├── Controllers/
│   │   ├── Controller.php
│   │   ├── LoginController.php          # Shared auth entry points
│   │   ├── GeneralController.php      # Public lookups (country codes, splash)
│   │   ├── admin/                     # Admin dashboard APIs
│   │   │   ├── DashboardController.php
│   │   │   ├── UserController.php
│   │   │   ├── VendorController.php   # Future phase
│   │   │   ├── DrawController.php
│   │   │   ├── GameConfigController.php
│   │   │   ├── TicketController.php
│   │   │   ├── WinnerController.php
│   │   │   ├── WalletController.php
│   │   │   ├── WithdrawalController.php
│   │   │   ├── ReportController.php
│   │   │   ├── CMSController.php
│   │   │   ├── AnnouncementController.php
│   │   │   ├── SettingsController.php
│   │   │   └── AuditLogController.php
│   │   ├── customer/                  # Flutter + website authenticated APIs
│   │   │   ├── AuthController.php
│   │   │   ├── HomeController.php
│   │   │   ├── TicketController.php
│   │   │   ├── WalletController.php
│   │   │   ├── ResultController.php
│   │   │   ├── ProfileController.php
│   │   │   ├── NotificationController.php
│   │   │   └── PaymentController.php
│   │   ├── vendor/                    # Future phase — agent/vendor POS APIs
│   │   │   └── (not implemented in Phase 1)
│   │   └── website/                   # Public website (no auth)
│   │       ├── HomeController.php
│   │       ├── CMSController.php
│   │       ├── ResultController.php
│   │       └── TicketVerifyController.php
│   ├── Middleware/
│   │   ├── EnsureUserRole.php         # role_id guard
│   │   ├── EnsureDrawOpen.php         # betting cutoff
│   │   ├── SetLocale.php              # Accept-Language / user locale
│   │   └── LogAdminActivity.php
│   └── Requests/
│       ├── Admin/
│       ├── Customer/
│       └── Vendor/                    # Future phase
├── Models/
│   ├── User.php
│   ├── service/
│   │   └── ResponseSender.php
│   ├── admin/
│   ├── customer/
│   ├── vendor/
│   └── lottery/                       # Draw, Ticket, GameType, etc.
├── Services/
│   └── Lottery/
│       ├── DrawEngine.php             # 7-digit extraction
│       ├── PayoutCalculator.php       # Per-game win logic
│       ├── TicketService.php          # Create tickets + risk checks
│       ├── RiskControlService.php     # Min/max bet, number caps
│       ├── DrawAssignmentService.php  # Auto-assign next draw
│       └── SpecialBetResolver.php     # Grap, Maryaj, Grap Pè L3
├── Services/
│   ├── WalletService.php
│   ├── Payment/
│   │   ├── MonCashGateway.php
│   │   ├── PayPalGateway.php
│   │   └── CardGateway.php
│   ├── OtpService.php
│   ├── NotificationService.php
│   └── TicketCodeGenerator.php
├── Jobs/
│   ├── ProcessDrawResultsJob.php
│   ├── CreditWinningsJob.php
│   ├── SendPushNotificationJob.php
│   └── ProcessPaymentWebhookJob.php
├── Events/
├── Listeners/
├── Policies/
└── Support/
    ├── AdminScope.php
    └── ApiMessage.php                 # Multi-lang message builder

routes/
├── api.php                            # Main entry — requires sub-files
├── admin.php
├── customer.php
├── vendor.php                         # Future phase (file may exist stubbed; not loaded in Phase 1)
├── website.php
├── web.php                            # Payment redirect pages only
└── console.php                        # Scheduled draw cutoffs

resources/lang/
├── en/
│   ├── success.php
│   ├── error.php
│   ├── validation.php
│   └── web-error.php
├── fr/
│   └── (same structure)
└── ht/                                # Haitian Creole
    └── (same structure)

docs/
├── DATABASE_DESIGN.md
└── API_ARCHITECTURE.md
```

---

## 4. Routing Strategy

### 4.1 `bootstrap/app.php`

```php
->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'role' => \App\Http\Middleware\EnsureUserRole::class,
        'draw.open' => \App\Http\Middleware\EnsureDrawOpen::class,
        'locale' => \App\Http\Middleware\SetLocale::class,
        'admin.log' => \App\Http\Middleware\LogAdminActivity::class,
    ]);
});
```

### 4.2 `routes/api.php`

```php
<?php

use Illuminate\Support\Facades\Route;

require base_path('routes/website.php');
require base_path('routes/customer.php');
require base_path('routes/admin.php');

// Phase 2+ (vendor): require base_path('routes/vendor.php');
```

### 4.3 Route Prefixes & Auth

| File | Prefix | Auth | Role | Phase |
|------|--------|------|------|-------|
| `website.php` | `website/` | None (public) | — | 1 |
| `customer.php` | `customer/` | Sanctum | `role_id = 2` | 1 |
| `admin.php` | `admin/` | Sanctum | `role_id = 1` | 1 |
| `vendor.php` | `vendor/` | Sanctum | `role_id = 3` | **Future** |

**Base URL:** `https://api.norbizlotto.ht/api/` (configure per environment)

---

## 5. Standard API Response Format

Following `saimpex-backend` / `airotrack-backend` `ResponseSender`.

**Rule:** `message` is **always** a multi-language object — never a plain string. Every response (success, validation error, business error, auth error) must include `en`, `fr`, and `ht` (Haitian Creole). Clients pick the string for the active locale; all three are returned on every call.

Supported locale suffixes: `_en`, `_fr`, `_ht`.

### Success (general message)

Use `message_{locale}` keys for non-field-specific responses (login success, ticket purchased, etc.):

```json
{
  "status": "true",
  "data": { },
  "message": {
    "message_en": ["Ticket purchased successfully"],
    "message_fr": ["Billet acheté avec succès"],
    "message_ht": ["Tikè achte avèk siksè"]
  }
}
```

### Validation / field error

Use `{field}_{locale}` keys when the error is tied to a request field:

```json
{
  "status": "false",
  "data": [],
  "message": {
    "mobile_en": ["Phone number is required"],
    "mobile_fr": ["Le numéro de téléphone est requis"],
    "mobile_ht": ["Nimewo telefòn obligatwa"]
  }
}
```

### Business / auth error (no specific field)

Use `message_{locale}` keys (same as success):

```json
{
  "status": "false",
  "data": [],
  "message": {
    "message_en": ["Betting is closed for this draw"],
    "message_fr": ["Les paris sont fermés pour ce tirage"],
    "message_ht": ["Paryaj fèmen pou tiraj sa a"]
  }
}
```

### Multiple errors

Combine field keys and general keys in the same `message` object when needed:

```json
{
  "status": "false",
  "data": [],
  "message": {
    "bet_amount_en": ["Minimum bet is 5 HTG"],
    "bet_amount_fr": ["La mise minimum est de 5 HTG"],
    "bet_amount_ht": ["Paryaj minimòm se 5 HTG"],
    "message_en": ["Unable to complete purchase"],
    "message_fr": ["Impossible de finaliser l'achat"],
    "message_ht": ["Pa kapab fini acha a"]
  }
}
```

### HTTP Status Codes

| Code | Usage |
|------|-------|
| `200` | Success |
| `401` | Unauthenticated / invalid token |
| `403` | Wrong role or blocked user |
| `422` | Validation / business rule failure |
| `429` | OTP rate limit |
| `500` | Server error (generic message to client) |

### `ResponseSender` implementation

Reuse the same static helper as saimpex, with messages built via `ApiMessage` helper:

```php
// General success
ResponseSender::send('true', $data, ApiMessage::success('ticket_purchased'), 200);

// Field validation (from Validator)
ResponseSender::send('false', [], ApiMessage::validation($validator->errors()), 422);

// Business error
ResponseSender::send('false', [], ApiMessage::error('betting_closed'), 422);
```

**`ApiMessage` helper** reads from `resources/lang/{en,fr,ht}/success.php` and `error.php`, and always returns the three-locale array shape:

```php
// resources/lang/en/success.php → 'ticket_purchased' => 'Ticket purchased successfully'
ApiMessage::success('ticket_purchased');
// Returns:
// [
//   'message_en' => ['Ticket purchased successfully'],
//   'message_fr' => ['Billet acheté avec succès'],
//   'message_ht' => ['Tikè achte avèk siksè'],
// ]
```

**Client display logic:** use `users.preferred_locale` or `Accept-Language` / `?lang=` to choose which key to show (`message_en` vs `message_fr` vs `message_ht`, or `{field}_en` etc.).

---

## 6. Authentication

### 6.1 Admin (`role_id = 1`)

| Endpoint | Method | Auth |
|----------|--------|------|
| `admin/login` | POST | Public |
| `admin/logout` | GET | Sanctum |
| `admin/me` | GET | Sanctum |

**Payload:** `username`, `password`  
**Returns:** user details, module privileges, Sanctum token  
**Future:** 2FA via `admin_profiles.two_factor_enabled`

### 6.2 Customer (`role_id = 2`)

| Endpoint | Method | Auth |
|----------|--------|------|
| `customer/sendOtp` | POST | Public |
| `customer/verifyOtp` | POST | Public |
| `customer/register` | POST | Public (post-OTP) |
| `customer/logout` | GET | Sanctum |

**Flow:**
1. Send OTP to `country_code_id` + `mobile` (+509 default)
2. Verify OTP → create user if new, enforce 18+ on registration
3. Issue Sanctum token
4. Resend cooldown: 30 seconds (from scope/UI)

### 6.3 Vendor (`role_id = 3`) — Future phase

> **Not in Phase 1.** The Vendor role is seeded in `roles` for schema consistency. Vendor login, `routes/vendor.php`, and agent sell flows will be added in a later phase. Until then, tickets are sold only via customer app/website (`channel = 1` or `2`); `vendor_id` on tickets remains `NULL`.

Planned endpoints (reference only): `vendor/login`, `vendor/sendOtp`, `vendor/verifyOtp`, `vendor/logout`, `vendor/tickets/sell`, etc. See [§8.3](#83-vendor--routesvendorphp--future-phase).

---

## 7. Middleware Stack

| Middleware | Applied to | Purpose |
|------------|-----------|---------|
| `auth:sanctum` | Protected routes | Token validation |
| `role:1` | Admin routes | Admin only |
| `role:2` | Customer routes | Customer only |
| `role:3` | Vendor routes | Vendor only (**future phase**) |
| `locale` | All API | Set `app()->setLocale()` from header or user |
| `draw.open` | Ticket purchase | Reject if past cutoff |
| `throttle:otp` | OTP endpoints | Abuse prevention |
| `admin.log` | Admin mutations | Write `audit_logs` |

### `EnsureUserRole` example

```php
// role:1,2 or role:2
if (!in_array(auth()->user()->role_id, $roles)) {
    return ResponseSender::send(false, [], __('error.unauthorized'), 403);
}
```

---

## 8. API Modules — Endpoint Map

### 8.1 Website (Public) — `routes/website.php`

No authentication. Used by marketing site and unauthenticated browsing.

| Group | Endpoints | Description |
|-------|-----------|-------------|
| **Home** | `GET website/home` | Banners, upcoming draws, jackpots |
| **CMS** | `GET website/about`, `terms`, `privacy`, `responsible-gaming`, `how-to-play` | Static pages (en/fr/ht via `?lang=`) |
| **FAQ** | `GET website/faqs` | FAQ list |
| **Results** | `GET website/results`, `results/{draw_id}` | Public draw results |
| **Verify** | `GET website/ticket/verify?code=` | Ticket lookup by code |
| **Draws** | `GET website/draws/upcoming` | Countdown timers |
| **Tchala** | `GET website/tchala/search?q=` | Dream lookup |
| **Contact** | `POST website/contact` | Contact form |

---

### 8.2 Customer — `routes/customer.php`

Prefix: `customer/`. Auth: Sanctum + `role:2`.

#### Auth & Onboarding

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `sendOtp` | Send login OTP |
| POST | `verifyOtp` | Verify + token |
| POST | `register` | Complete profile (name, DOB) |
| GET | `splash` | Splash config, languages |
| GET | `logout` | Revoke token |

#### Home & Games

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `home` | Wallet, draws, game cards, countdown |
| GET | `games` | Active games + current multipliers |
| GET | `games/{slug}/rules` | Rules, prizes, FAQ tabs |
| GET | `draws/upcoming` | Next draw sessions |
| GET | `tchala/search` | Tchala lookup |

#### Ticket Purchase

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `tickets/validate` | Pre-check numbers, limits, totals |
| POST | `tickets/cart/preview` | Cart summary (multi-ticket) |
| POST | `tickets/purchase` | Confirm purchase (wallet or redirect payment) |
| GET | `tickets/{code}` | Ticket detail |
| GET | `tickets` | My tickets (active/past filters) |
| GET | `tickets/{code}/receipt` | PDF receipt download |

**Purchase payload example:**

```json
{
  "draw_session_id": 1,
  "payment_method": "wallet",
  "lines": [
    {
      "game_type": "borlette",
      "number_primary": "47",
      "bet_amount": 100
    },
    {
      "game_type": "marriage",
      "number_primary": "47",
      "number_secondary": "28",
      "bet_amount": 50
    },
    {
      "game_type": "loto3",
      "number_primary": "472",
      "bet_amount": 25
    },
    {
      "game_type": "grap",
      "bet_amount": 10
    },
    {
      "game_type": "maryaj_combo",
      "numbers": ["45","56","22","65"],
      "maryaj_amount": 25
    }
  ]
}
```

#### Results

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `results/latest` | Latest draw + extracted positions |
| GET | `results` | History (paginated, filter by date/session) |
| GET | `results/search` | Search by ticket code or date |

#### Wallet

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `wallet` | Balance + currency |
| GET | `wallet/transactions` | History |
| POST | `wallet/topup` | Initiate deposit (gateway redirect) |
| POST | `wallet/withdraw` | Submit withdrawal request |
| GET | `wallet/withdrawals` | Withdrawal history |

#### Payments

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `payments/initiate` | Start MonCash/PayPal/card flow |
| GET | `payments/{id}/status` | Poll payment status |
| POST | `payments/webhook/moncash` | Gateway callback (also in web.php) |
| POST | `payments/webhook/paypal` | Gateway callback |

#### Profile & Settings

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `profile` | User profile |
| POST | `profile/update` | Edit name, DOB, image |
| POST | `profile/language` | Set `preferred_locale` |
| POST | `profile/currency` | Set `preferred_currency` |
| GET | `notifications` | In-app notifications |
| POST | `notifications/read` | Mark read |
| POST | `device/register` | FCM token |

---

### 8.3 Vendor — `routes/vendor.php` — Future phase

> **Not in Phase 1.** Documented for future agent/POS app. Do not implement or expose these routes until the vendor phase is scheduled.

Prefix: `vendor/`. Auth: Sanctum + `role:3`.

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `login` | Vendor login |
| GET | `dashboard` | Sales today, float balance, active draw |
| GET | `draws/current` | Current open draw + cutoff |
| POST | `tickets/validate` | Validate bet before sale |
| POST | `tickets/sell` | Create ticket for walk-in customer |
| GET | `tickets` | Sold tickets (filter by date/draw) |
| GET | `tickets/{code}` | Ticket lookup |
| GET | `results/latest` | Latest results for display |
| GET | `reports/sales` | Daily sales report |
| GET | `reports/commission` | Commission history |
| GET | `profile` | Vendor profile + float |
| POST | `profile/update` | Update contact info |

**Vendor-specific UI flows (from scope):**
- **Grap** button → auto-expand to doubles 00–99
- **Grap Pè L3** button → auto-expand to triples 000–999
- **Maryaj** → auto-generate all pair combinations from entered numbers

---

### 8.4 Admin — `routes/admin.php`

Prefix: `admin/`. Auth: Sanctum + `role:1` + optional `admin.log`.

#### Dashboard

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `login` | Admin login (in api.php root or admin.php) |
| GET | `dashboard` | KPIs: users, tickets sold, revenue, pending withdrawals |
| GET | `logout` | Logout |

#### User Management

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `users` | List/search customers |
| GET | `users/{id}` | Detail + KYC + wallet |
| POST | `users` | Create user |
| POST | `users/update` | Edit user |
| GET | `users/updateStatus` | Block/unblock |
| POST | `users/wallet-adjust` | Manual wallet adjustment |
| GET | `users/{id}/transactions` | Wallet history |

#### Vendor Management — Future phase

> **Not in Phase 1.** Admin vendor CRUD, commissions, and agent reports deferred to the vendor phase.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `vendors` | List agents |
| POST | `vendors` | Create vendor |
| POST | `vendors/update` | Edit vendor |
| GET | `vendors/updateStatus` | Activate/suspend |
| POST | `vendors/commission` | Set commission rate |
| GET | `vendors/{id}/sales` | Sales report |

#### Game & Risk Configuration

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `games` | Game types list |
| GET | `games/config` | Current multipliers + limits |
| POST | `games/config/update` | Update multiplier/limit (logged) |
| GET | `games/config/logs` | Config change history |
| GET | `games/updateStatus` | Enable/disable game |

#### Draw Management

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `lottery-sources` | NY, FL, GA, etc. |
| GET | `draw-sessions` | Session schedules |
| POST | `draw-sessions` | Create/update session |
| GET | `draws` | Draw list (filter date/status) |
| GET | `draws/{id}` | Draw detail + positions |
| POST | `draws/{id}/result` | Enter Pick3 + Pick4 results |
| POST | `draws/{id}/verify` | Second admin verification |
| POST | `draws/{id}/process` | Trigger winner processing |
| GET | `draws/{id}/report` | Draw financial report |

#### Ticket & Winner Management

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `tickets` | All tickets (filters) |
| GET | `tickets/export` | Excel export |
| GET | `tickets/{code}` | Ticket detail |
| GET | `winners` | Winners list |
| POST | `winners/{id}/override` | Manual payout override |
| POST | `winners/{id}/mark-paid` | Mark as paid |

#### Financial

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `withdrawals` | Pending withdrawal requests |
| POST | `withdrawals/approve` | Approve |
| POST | `withdrawals/reject` | Reject |
| GET | `reports/revenue` | Revenue by period |
| GET | `reports/gateway` | Gateway-wise report |
| GET | `reports/game` | Per-game breakdown |
| GET | `reports/export` | CSV/PDF export |

#### CMS

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET/POST | `cms/{page}` | Terms, privacy, about, rules |
| GET/POST | `faqs` | FAQ CRUD |
| GET/POST | `banners` | Banner CRUD |
| GET/POST | `announcements` | Announcements |

#### Settings & Audit

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `settings` | Platform settings |
| POST | `settings/update` | Update fees, maintenance, versions |
| GET | `audit-logs` | Admin activity |
| GET | `login-logs` | Login history |
| GET | `draw-logs` | Draw result audit |

---

## 9. Core Service Layer

### 9.1 `DrawEngine`

Responsible for transforming raw results into winning positions.

```
Input:  pick3 = "472", pick4 = "8316"
Output: combined = "4728316"
        positions = {
          pair_1: "47", pair_2: "28", pair_3: "31",
          loto3_a: "472", loto3_b: "316",
          loto4_a: "4728", loto4_b: "8316",
          loto5: [ "472-47", "472-28", "472-31", "316-47", "316-28", "316-31" ]
        }
```

Persist to `draw_winning_positions`. Immutable after draw finalized.

### 9.2 `PayoutCalculator`

Game-specific win detection:

| Game | Win condition |
|------|---------------|
| **Borlette** | `number_primary` equals any of 3 pairs |
| **Marriage** | Both `number_primary` AND `number_secondary` appear in the 3 pairs (order irrelevant) |
| **Loto 3** | Exact match on `loto3_a` or `loto3_b` |
| **Loto 4** | Exact match on `loto4_a` or `loto4_b` |
| **Loto 5** | First 3 digits match any Loto3 position AND last 2 match any pair |
| **Grap** | Winning pair is a double (00,11,…,99) AND matches expanded bet |
| **Grap Pè L3** | Winning Loto3 is triple AND matches expanded bet |
| **Maryaj combo** | Each generated pair combo wins independently if both numbers in draw pairs |

**Payout:** `bet_amount × multiplier_snapshot` (highest position only for multi-match Borlette — confirm with PO).

### 9.3 `RiskControlService`

Before ticket creation:
1. `bet_amount >= min_bet`
2. `bet_amount <= max_bet_per_number`
3. `number_sales_tracker.total + bet <= max_total_sales_per_number`
4. Draw status = `open`
5. Server time < cutoff

### 9.4 `TicketService`

Orchestrates:
1. Validate lines via `RiskControlService` + `SpecialBetResolver`
2. Snapshot multipliers from `game_configurations`
3. Assign draw via `DrawAssignmentService`
4. Deduct wallet / create `payment_transaction`
5. Generate `ticket_code`
6. Update `number_sales_tracker`
7. Dispatch confirmation notification

### 9.5 `WalletService`

All balance mutations through DB transactions:
- `credit(user, amount, source_type, source_id)`
- `debit(user, amount, source_type, source_id)`
- Never update balance without a `wallet_transactions` row

---

## 10. Background Jobs & Scheduling

### 10.1 Scheduled Commands (`routes/console.php`)

| Schedule | Command | Purpose |
|----------|---------|---------|
| Every minute | `draws:close-expired` | Close betting at cutoff per session |
| Every 5 min | `payments:reconcile-pending` | Poll stale payments |
| Daily | `reports:aggregate-daily` | Pre-compute dashboard stats |
| Optional | `draws:fetch-results` | Auto-fetch US lottery API |

### 10.2 Queued Jobs

| Job | Trigger | Action |
|-----|---------|--------|
| `ProcessDrawResultsJob` | Admin enters result | Extract positions, evaluate all tickets |
| `CreditWinningsJob` | After processing | Credit wallets for winners |
| `SendPushNotificationJob` | Ticket/win/wallet events | FCM push |
| `ProcessPaymentWebhookJob` | Gateway callback | Verify + complete purchase |

---

## 11. Payment Integration

### Supported Gateways (Phase 1)

| Gateway | Flow | Webhook |
|---------|------|---------|
| **Wallet** | Internal debit | N/A |
| **MonCash** | Redirect / WebView | `POST /api/payments/webhook/moncash` |
| **PayPal** | Redirect | `POST /api/payments/webhook/paypal` |
| **Visa/Mastercard** | Via payment provider | Provider-specific |

### Payment flow

```
Client → POST customer/payments/initiate
       → Backend creates payment_transaction (status: initiated)
       → Returns redirect_url
Client → Completes payment on gateway
Gateway → Webhook → ProcessPaymentWebhookJob
         → Mark payment success
         → Create ticket OR credit wallet
         → Send receipt notification
```

Idempotency: store `gateway_reference` unique; ignore duplicate webhooks.

---

## 12. Multi-Language Support

### Locales

| Code | Language |
|------|----------|
| `en` | English |
| `fr` | French |
| `ht` | Haitian Creole |

### Implementation

1. **User preference:** `users.preferred_locale`
2. **Request override:** `Accept-Language` header or `?lang=ht`
3. **CMS content:** `*_en`, `*_fr`, `*_ht` columns
4. **API messages:** All `message` values in API responses use the multi-language object format (see §5). Strings live in `resources/lang/{en,fr,ht}/` and are assembled by `ApiMessage`.
5. **Notifications:** Store all three locales in `notifications` table; client picks based on locale

### Message key conventions

| Scenario | Key pattern | Example |
|----------|-------------|---------|
| Success / general error | `message_{locale}` | `message_en`, `message_fr`, `message_ht` |
| Field validation | `{field}_{locale}` | `mobile_en`, `bet_amount_ht` |

All values are **arrays of strings** (supports multiple errors per field), matching saimpex conventions.

---

## 13. Security Requirements

| Area | Rule |
|------|------|
| **Tickets** | Immutable after cutoff; no client-side timestamps |
| **Draw results** | Tamper-proof logs; optional dual admin approval |
| **Vendors** (future) | Cannot change multipliers or limits |
| **Admins** | All config changes logged |
| **OTP** | Hashed storage, expiry, attempt limits, 30s resend |
| **Tokens** | Sanctum; revoke on logout |
| **Webhooks** | Signature verification per gateway |
| **KYC** | DOB 18+ enforced server-side |
| **Rate limiting** | OTP, login, ticket purchase endpoints |

---

## 14. Real-Time Features

| Feature | Implementation |
|---------|----------------|
| Draw countdown | Client-side timer; server provides `betting_closed_at` |
| Draw lock | Server rejects purchases after cutoff (authoritative) |
| Results refresh | Poll `results/latest` or WebSocket (phase 2) |
| Push notifications | FCM via `user_devices` |

---

## 15. Development Phases

### Phase 1 — Foundation *(current)*
- [ ] Users, roles (seed Admin, Customer, **Vendor** — vendor APIs not built yet)
- [ ] OTP auth: **admin** (username/password) + **customer** (phone/OTP) only
- [ ] Settings, CMS pages, multi-language
- [ ] Lottery sources, draw sessions, game config
- [ ] Wallet (internal only)
- [ ] Routes: `website`, `customer`, `admin` only — **no `vendor.php`**

### Phase 2 — Core Gaming
- [ ] Ticket purchase via customer app & website (all 5 games + Grap/Maryaj/Grap Pè L3)
- [ ] Risk control & number sales tracking
- [ ] Draw result entry & `DrawEngine`
- [ ] Winner processing & wallet credit

### Phase 3 — Payments
- [ ] MonCash, PayPal, card integration
- [ ] Withdrawal approval flow

### Phase 4 — Admin & Reporting
- [ ] Full admin dashboard APIs
- [ ] Reports & exports
- [ ] Audit logs, login logs
- [ ] Tchala dictionary

### Phase 5 — Automation
- [ ] US lottery API auto-fetch
- [ ] Push notifications
- [ ] PDF receipts

### Phase 6 — Vendor / Agent network *(future)*
- [ ] `routes/vendor.php` + vendor auth (login / OTP)
- [ ] Vendor POS: sell tickets, Grap / Maryaj / Grap Pè L3 flows
- [ ] Admin vendor management, commissions, float, agent reports
- [ ] `vendor_id` on tickets, `channel = 3`

---

## 16. Environment Variables

```env
APP_NAME="Norbiz Lotto"
APP_URL=https://api.norbizlotto.ht
APP_LOCALE=en
APP_FALLBACK_LOCALE=en

DB_CONNECTION=mysql
DB_DATABASE=norbiz_lotto

SANCTUM_STATEFUL_DOMAINS=admin.norbizlotto.ht,app.norbizlotto.ht

OTP_EXPIRY_MINUTES=5
OTP_RESEND_SECONDS=30

MONCASH_CLIENT_ID=
MONCASH_CLIENT_SECRET=
MONCASH_WEBHOOK_SECRET=

PAYPAL_CLIENT_ID=
PAYPAL_CLIENT_SECRET=

FCM_SERVER_KEY=

QUEUE_CONNECTION=redis
```

---

## 17. Testing Strategy

| Layer | Tool | Focus |
|-------|------|-------|
| Unit | PHPUnit | `DrawEngine`, `PayoutCalculator`, `RiskControlService` |
| Feature | PHPUnit | Auth flows, ticket purchase, cutoff enforcement |
| Integration | PHPUnit + DB | Full draw lifecycle: bet → result → payout |
| Contract | Postman/Scribe | API docs per client team |

**Critical test cases:**
- 7-digit extraction matches PDF examples (`4728316`)
- Maryaj generates 6 combinations from 4 numbers
- Grap expands to 10 doubles
- Multiplier snapshot preserved after admin config change
- No bets accepted after cutoff
- Unlimited winners on same number

---

## 18. API Documentation Tooling

Recommend **Laravel Scribe** or **OpenAPI (Swagger)** generated from route annotations. Group docs by:
- Website (Public)
- Customer App
- Customer Website
- Admin
- Vendor *(future — document when Phase 6 starts)*

---

## 19. Cross-Reference: UI → API Mapping

| UI Screen (Figma / Admin design) | Primary endpoints |
|--------------------------------|-------------------|
| Splash / Language | `customer/splash`, `website/home` |
| Login / OTP | `customer/sendOtp`, `verifyOtp` |
| Home / Wallet | `customer/home`, `wallet` |
| Select Numbers | `customer/games`, `tickets/validate` |
| Cart / Checkout | `tickets/cart/preview`, `tickets/purchase` |
| Ticket Success | `tickets/{code}` |
| My Tickets | `customer/tickets` |
| Results | `customer/results` |
| Profile | `customer/profile` |
| Admin Dashboard | `admin/dashboard` |
| Admin Draw Entry | `admin/draws/{id}/result` |
| Admin Users | `admin/users` |

---

*Document maintained in `docs/API_ARCHITECTURE.md`. Pair with `docs/DATABASE_DESIGN.md` for schema details.*
