# Module: subpayment (`b2g_subpayment/`)
Purpose: Thawani (Oman) payment checkout for subscription invoices — creates a hosted checkout session, handles success/cancel/webhook and reports the result back to usermgmt. NOT `b2g_subscriptnmgmt` (the name frontends reference); this is the deployed payment service. Owner: Sanjay.
Entry points: `web/index.php` → `config/web.php`; `modules/thawani/controllers/PaymentController.php` (`checkout`, `success`, `cancel`, `webhook`), `modules/thawani/components/ThawaniApi.php` (module component `api`), views in `modules/thawani/views/payment/`; `modules/api/controllers/WebserviceController.php` is a stub returning canned JSON.
Data: `bgi_b2g_reg_{dev|prod}` (`config/db-*.php`) — key table: `memcompthawanipaymentsdtls_tbl` (`mctpd_sessionid`, `mctpd_status` 1=pending/2=paid, `mctpd_memcompinvoicedtls_fk` → invoice row owned by usermgmt's subscription module); migration `m231024_*` creates an older `thawani_payments` table (verify still used).
Calls: Thawani Checkout API (`checkout.thawani.om` live / `uatcheckout.thawani.om` uat, chosen by `params['thawani']['mode']`); usermgmt `subm/subscription-management/update-payment-status` via `USERMGMT_BASE_URL` → `updatePaymentStatusUrl`; micro `master/master/getmodules|getsubscriptionplans` via `MICRO_BASE_URL`; `FRONTEND_DASHBOARD_URL` for the back-to-dashboard link. Overrides in `config/params-{env}.php`. Inbound: usermgmt redirects the browser here using `SUBPAYMENT_BASE_URL` (browser-facing).
Env selection: `APP_ENV` first, else hostname (`demo.blu2green.earth`→demo, `blu2green.earth`→prod, else local) — see `config/web.php`; loads `params-{env}.php` + `db-{env}.php`.
Invariants:
- `params-prod.php` must stay `thawani.mode => 'live'`, demo `'uat'`; amounts are in baisas (1 OMR = 1000) and split into ≤5,000,000-baisa line items.
- `client_reference_id` format `INV-<invoiceId>-<ts>` is parsed back to the invoice id — don't change one side only.
- Never copy the Thawani secret/publishable keys committed in params files; move to Secrets Manager.
Gotchas:
- `webhook` does no signature/source verification — it trusts any `payment_successful` POST with a known `session_id` (the `success` path does re-verify via `getSession`).
- `ThawaniApi::request()` curl has no timeout (listed in `ci/curl-timeout-baseline.txt`); the usermgmt callback uses a 30s timeout, longer than Service Connect's 15s.
- `config/web.php` registers modules `master` and `drv` whose directories do not exist — any route into them fatals.
- `vendor/` not committed: `setup.sh`/`setup.bat` run `composer install` for it; the Dockerfile `deps` stage does it for images. Never run this folder's own `docker-compose.yml` (7.4-apache on :8000, collides with Kong).
- Only `params-/db-{local,demo,prod}.php` exist while compose sets `APP_ENV: dev` (verify local boot); stderr mirror is the `FileTarget` → `/proc/self/fd/2` pattern (verify against root CLAUDE.md).
Prod debugging: CloudWatch `/ecs/b2g-prod/subpayment` (demo: `/ecs/b2g-demo/subpayment`); health `GET /healthz` on :80; ECS service `subpayment`, task family `b2g-subpayment` / `b2g-demo-subpayment`. Runbooks: `docs/runbooks/`.
