# BIPL Takaful Portal &mdash; Backend & Operations Portal

Laravel 12 backend for the BankIslami (BIPL) &times; EFU Window Takaful bancatakaful programme.
It serves two consumers:

1. **A JSON API** for the customer-facing React journey at `https://bipl.efulife.com/?data=<encrypted>`.
2. **A server-rendered operations portal** with three role-based views: Admin, EFU Operations and BIPL Operations.

---

## The business flow

```
  BankIslami internet banking
        │  links the customer over with an encrypted payload
        ▼
  React journey  ──POST /api/v1/entry/resolve──►  decrypt, recognise or register the
                                                  customer, issue a journey token
        │
        │  customer browses products, picks a plan, gives consent (OTP verified)
        ▼
  POST /api/v1/subscriptions  ───►  subscription created with status = lead
        │
        ▼
  EFU Ops portal  ──"Generate lead extract"──►  daily CSV, leads become `exported`
        │
        ▼
  BIPL Ops load the CSV into the bank system and run the CBC calls
        │
        ▼
  BIPL Ops portal  ──upload CBC result file──►  confirmed  → `deduction_pending`
                                                            (first instalment raised)
                                                rejected   → `cbc_rejected`
        │
        ▼
  BIPL Ops portal  ──upload successful-deduction file──►  policy number issued,
                                                          cover dates set, status = `active`
```

Every status change is validated against an explicit transition table and written to
`subscription_status_logs`, so a malformed upload cannot push a record into an impossible state.

---

## Getting started

Requirements: PHP 8.2+, Composer 2, MySQL/MariaDB, Node 20.19+ (for asset builds).

```bash
composer install
npm install

cp .env.example .env
php artisan key:generate

# create the schema and load the nine Schedule 1 products
php artisan migrate --seed

npm run build
php artisan serve
```

The portal is at <http://localhost:8000/login>. Seeded accounts (password `Password@123`):

| Role | Email |
|---|---|
| Administrator | `admin@efulife.com` |
| EFU Operations | `efu.ops@efulife.com` |
| BIPL Operations | `bipl.ops@bankislami.com.pk` |

### Generate a test journey link

The bank's integration is not needed to exercise the journey:

```bash
php artisan bipl:make-link
php artisan bipl:make-link --cnic=4210155555551 --name="Ali Raza" --mobile=03211234567
```

It prints the plaintext payload, the ciphertext, a ready-to-open journey URL and a `curl`
command against `/api/v1/entry/resolve`.

### Scheduled work

```bash
php artisan takaful:extract-leads     # build the daily extract manually
php artisan schedule:work             # or run the scheduler
```

The scheduler builds the extract at `LEAD_EXPORT_CUTOFF` (default 23:00) and prunes
expired journey sessions weekly.

---

## Configuration

Domain settings live in two config files rather than being scattered through the code.

**`config/bipl.php`** &mdash; everything about the bank integration:

| Key | Purpose |
|---|---|
| `crypto.*` | Cipher, shared key, IV, transport encoding for the hand-off payload |
| `signature.*` | Optional HMAC-SHA256 check on the ciphertext |
| `payload_ttl` | Reject payloads whose `ts` claim is older than N seconds |
| `allow_replay` | Whether the same ciphertext may be resolved more than once |
| `payload_map` | Alias table mapping the bank's field names to ours |
| `session.ttl` | Journey token lifetime |
| `otp.*` | OTP length, TTL, attempts, test mode |
| `files.*` | Disk, paths and upload limits for the file exchange |

**`config/takaful.php`** &mdash; product and operational rules: eligibility ages, free look period,
reference-number formats, the Schedule 2 remuneration table, the tax/levy breakdown, the lead
export column order, and the accepted header aliases for inbound files.

Operational switches that need to change without a deployment (journey open/closed, OTP
requirement, max plans per customer, retry windows) live in the `settings` table and are
editable at **Settings** in the admin portal.

---

## Security notes

- **CNIC, mobile, account number and nominee CNIC are encrypted at rest** using Laravel's
  encrypter. Each has a keyed blind index (`*_hash`, HMAC-SHA256 under `APP_KEY`) so lookups
  remain a single indexed query without storing the plaintext. See `app/Support/Identity.php`.
- **Customers are not portal users.** The journey uses its own short-lived bearer token, stored
  hashed in `customer_sessions`, issued per landing and revocable independently.
- **Every landing is recorded**, including failed decryptions, with the reason, IP and
  user agent &mdash; useful when the bank reports a broken link.
- **Consent is evidenced** in a separate immutable `consents` table holding the exact wording
  accepted, its hash, the channel, IP, user agent and OTP status.
- **Uploaded files are hashed**; re-uploading an identical file is rejected as a duplicate.

Before go-live, replace `BIPL_CRYPTO_KEY` / `BIPL_CRYPTO_IV` with the values exchanged with
BankIslami, and set `BIPL_ALLOW_REPLAY=false` and `OTP_TEST_MODE=false`.

---

## Portal design system

The palette is sampled from the BankIslami logo and declared once in
[`resources/css/app.css`](resources/css/app.css) as Tailwind 4 theme tokens:

| Token | Hex | Used for |
|---|---|---|
| `brand-600` | `#0061a8` | Primary actions, active navigation, the wordmark blue |
| `aqua-500` | `#00a9e0` | Secondary accent, the calligraphy blue |
| `leaf-500` | `#8dc63f` | Positive outcomes only &mdash; active policies, applied rows |
| `canvas` | `#f4f7fb` | Application background |

Green is deliberately held back for success states so it keeps its meaning on a dashboard.

Shared classes (also in `app.css`) rather than long class lists in every view:
`.field`, `.field-label`, `.check`, `.btn-primary` / `.btn-secondary` / `.btn-success` /
`.btn-danger` / `.btn-sm`, `.data-table`, `.empty-state`.

Blade components in [`resources/views/components/`](resources/views/components/):

| Component | Usage |
|---|---|
| `<x-logo size="h-7" />` | The BankIslami lockup (`public/images/bankislami-logo.png`) |
| `<x-icon name="shield" />` | Inline SVG icon set &mdash; no CDN or icon package |
| `<x-card title icon subtitle :actions :padded>` | Panel shell |
| `<x-stat label value hint tone icon :href>` | Dashboard tile; `tone` maps onto the palette |
| `<x-badge :variant="…">` | Status pill; `variant` carries the colour classes |
| `<x-alerts />` | Session status / warning / validation banners |

Status colours live on the enums (`SubscriptionStatus::badgeClass()` and `barClass()`,
`BatchStatus`, `ChargeStatus`, `CbcResult`, `UserRole`), so a status is coloured the same way
everywhere it appears.

To replace the logo, drop a new file at `public/images/bankislami-logo.png` &mdash; nothing else
references the image path.

---

## Documentation

- [`docs/api.md`](docs/api.md) &mdash; the JSON API contract for the React team
- [`docs/integration.md`](docs/integration.md) &mdash; payload format and file layouts for BankIslami
- [`docs/products.md`](docs/products.md) &mdash; the seeded Schedule 1 catalogue and known source discrepancies

---

## Tests

```bash
php artisan test
```

`SubscriptionJourneyTest` walks the entire flow once &mdash; encrypted link through to an active
policy &mdash; and covers the rejection paths (bad payload, wrong deduction amount, ineligible age,
unsupported frequency, role isolation). `PortalScreensTest` renders every portal screen with real
data so a broken view or a missing eager load fails the build.

---

## Project layout

```
app/
  Enums/                      SubscriptionStatus (with the transition table), BatchType, ...
  Http/Controllers/Api/V1/    the React journey API
  Http/Controllers/Portal/    the operations portal
  Http/Middleware/            EnsureCustomerSession (journey), EnsureUserRole (portal)
  Models/                     Customer, Subscription, Product, ProductPlan, Charge, FileBatch, ...
  Services/
    Bipl/                     PayloadCipher, PayloadMapper, EntryResolver
    Batch/                    LeadExportService, CbcImportService, DeductionImportService, CsvReader
    SubscriptionWorkflow      the only place a subscription status may change
    SubscriptionService       consent capture and lead creation
    PortalMetrics             the figures behind all three dashboards
  Support/                    Identity (encryption/blind index), DateParser, NumberGenerator
database/seeders/             the nine Schedule 1 products, plans, benefits and contributions
```
