# Messaging (OTP) & Online Payments — Setup Guide

This guide covers turning on **real SMS / WhatsApp OTP delivery** and the
**online EasyPaisa / JazzCash payment gateway**. Both are built into the
codebase and driven by configuration — you supply the provider accounts.

> While `APP_ENV=local`, the master OTP **`123456`** always works, so you can
> test every flow before any provider is connected. It is hard-disabled outside
> `local`.

---

## 1. SMS / WhatsApp / Push OTP

The notification engine (`App\Notifications\Notify` → `MessageDispatcher` →
channel drivers) is fully implemented. It tries channels left-to-right until one
succeeds and logs every attempt to the `message_logs` table (admin → **Message
log**).

### 1.1 Choose your channels

In `.env`:

```dotenv
# dev default — OTP is written to storage/logs/laravel.log
NOTIFY_CHANNELS=log

# production — WhatsApp first, fall back to SMS
NOTIFY_CHANNELS=whatsapp,sms

NOTIFY_QUEUE=sync          # 'database' to queue (then run: php artisan queue:work)
NOTIFY_NUMBER_FORMAT=as_is # as_is | local | pk (923…) | e164 (+923…)
```

### 1.2 SMS gateway

Works with any Pakistani aggregator that exposes an HTTP send API (Veevo Tech,
Branded SMS Pakistan, Telenor, Jazz, etc.). Map your provider's parameter names:

```dotenv
SMS_API_URL=https://api.yourprovider.com/send
SMS_API_METHOD=GET                # or POST
SMS_API_KEY=your_api_key
SMS_SENDER_MASK=Malakand          # your approved sender/mask
SMS_PARAM_TO={to}                 # provider's field name for the recipient
SMS_PARAM_MESSAGE={message}
SMS_PARAM_SENDER={sender}
SMS_PARAM_KEY={key}
SMS_SUCCESS_CONTAINS=             # optional substring proving success, e.g. "OK"
```

`{to} {message} {sender} {key}` are placeholders the driver fills in; the
left-hand side is **your provider's** parameter name.

### 1.3 WhatsApp

**Meta Cloud API** (recommended — get a Phone Number ID + permanent token from
the Meta Business / WhatsApp Manager):

```dotenv
WHATSAPP_FORMAT=meta
WHATSAPP_API_URL=https://graph.facebook.com/v21.0/<PHONE_NUMBER_ID>/messages
WHATSAPP_API_KEY=<permanent_access_token>
```

You must also **approve message templates** in WhatsApp Manager whose names match
the catalog in `app/Notifications/Messages.php` (e.g. `mlk_otp`,
`mlk_order_confirmed`, `mlk_order_on_the_way`, `mlk_order_delivered`,
`mlk_order_cancelled`, `mlk_order_returned`, `mlk_order_refunded`,
`mlk_vendor_new_order`, `mlk_courier_offer`). The positional body params
(`{{1}}`, `{{2}}`, …) must line up with the `params` in that file.

A generic BSP is also supported (`WHATSAPP_FORMAT=generic` + `WA_PARAM_*`).

### 1.4 Push (optional)

```dotenv
NOTIFY_CHANNELS=push,whatsapp,sms
FCM_SERVER_KEY=<firebase_server_key>
```

Device tokens register automatically from the apps. See `PUSH_NOTIFICATIONS.md`.

### 1.5 Test it

1. Set `NOTIFY_CHANNELS` and the provider vars, then `php artisan config:clear`.
2. Trigger an OTP (log in from any app).
3. Watch the phone, or admin → **Message log**, for the delivery + provider
   message id. On failure the log shows the provider's error.

---

## 2. Payments

### 2.1 Cash on Delivery & manual transfer — already live

Admin → **Payment methods** controls everything (stored in the `settings`
table; the public app reads `GET /api/payment-methods`):

- **COD** — toggle on/off, optional max order value.
- **EasyPaisa / JazzCash in `manual` mode** — show your account number; the
  customer transfers and types their **TID**, captured as
  `orders.payment_reference`. You confirm receipt with **Mark paid** on the
  order (admin → Orders), or **Refund** to reverse it to the customer's wallet.

No code or credentials needed for the above.

### 2.2 Online gateway (EasyPaisa / JazzCash `api` mode)

The end-to-end plumbing is built and tested; two provider-specific pieces remain
(clearly marked `TODO(provider)` in `app/Services/OnlinePaymentService.php`).

**Flow**

```
app → POST /api/customer/payment/{order}/initiate
        → OnlinePaymentService::initiate(): creates a PaymentToken, returns redirect_url
app → opens redirect_url (gateway hosted checkout)
gateway → POST /api/payment/callback/{gateway}   (server-to-server webhook — source of truth)
        → OnlinePaymentService::settle(): marks order paid/failed (idempotent)
customer browser → /api/payment/return/{gateway}?token=…  (UX only; mirrors result)
```

**Already done**

- `payment_tokens` table + `PaymentToken` model (tracks each in-flight payment).
- `OnlinePaymentService::initiate()` / `settle()` — token lifecycle, idempotent
  settlement, flips `orders.payment_status` + `payment_reference`.
- Routes + controllers (`PaymentGatewayController`, `PaymentCallbackController`).
- Credential fields in admin → **Payment methods** (store id / merchant id /
  hash key / integrity salt / sandbox|live), stored via the `Setting` model.

**You must complete** (against the official merchant docs):

1. **`initiate()` — build the real signed request.** Replace the self-hosted
   return URL with the gateway's hosted-checkout URL, signing the request with
   the admin Settings (`pay_easypaisa_store_id` / `pay_jazzcash_merchant_id`,
   `_hash_key` / `_integrity_salt`, `_env`).
2. **`settle()` — verify the callback signature.** Before trusting the result,
   validate the provider's checksum on the raw webhook payload with your secret.
   Also map the provider's real field names in `PaymentCallbackController`
   (`token`, status, `transaction_id`).

**Turn it on**

1. Admin → **Payment methods**: set EasyPaisa/JazzCash **mode = Online gateway**,
   fill the credentials, choose sandbox/live.
2. `.env`: `PAYMENTS_RETURN_BASE_URL=https://your-domain` (public HTTPS URL the
   gateway can reach).
3. Register the webhook URL (`https://your-domain/api/payment/callback/easypaisa`
   or `/jazzcash`) in the provider's merchant portal.

**Sandbox sanity check (before provider wiring):** with a pending order you can
hit `GET /api/payment/return/easypaisa?token=<token>&status=success` and confirm
the order flips to `paid` — this exercises the token → settle → order path.

> **Security:** the **webhook** is the source of truth and must verify the
> signature. The browser return URL is convenience only — never grant fulfilment
> on the return URL alone in production.
