# BEEB Notification Keys

Reference for in-app / push notification translation keys used by the BEEB platform.

## Source files

| File | Purpose |
|------|---------|
| `app/Enums/NotificationTypeEnum.php` | Canonical `type` values stored on notifications |
| `resources/lang/en/notification.php` | English title/body copy |
| `resources/lang/ar/notification.php` | Arabic title/body copy |
| `app/Traits/NotificationMessageTrait.php` | Resolves stored notifications to localized text |
| `app/Services/Notification/NotificationService.php` | Event-based notifications (`NotificationEvent`) |
| `app/Notifications/GlobalNotification.php` | Database + FCM notifications with `title_key` / `body_key` |

## How keys are resolved

### 1. `NotificationEvent` (rider / provider / staff)

Uses the pattern:

- **Title:** `notification.title_{type}`
- **Body:** `notification.body_{type}`

Example: type `ride_started` → `notification.title_ride_started` + `notification.body_ride_started`.

Defined in `NotificationService::notify()`.

### 2. `GlobalNotification` (database notifications)

Stored payload fields:

- `title_key` — lang key **without** the `notification.` prefix (e.g. `title_wallet_charge`)
- `body_key` — same for body (e.g. `body_wallet_charge`)
- `type` — `NotificationTypeEnum` value or custom string (`blocked`, `admin_notify`, …)
- `params` — placeholder values (`amount`, `name`, `id`, …)

Resolution order (`NotificationMessageTrait`):

1. `title_key` / `body_key` → `notification.{key}` or `notification.{key}_{locale}`
2. `title_{locale}` / `body_{locale}` stored on payload
3. Fallback: `notification.title_{type}` / `notification.body_{type}`

### 3. Entity-specific suffixed keys

Some observers use keys like `provider_approved_title` which resolve to:

- `notification.provider_approved_title_en` (English)
- `notification.provider_approved_title_ar` (Arabic)

Pattern: `{entity}_{action}_title_{locale}` / `{entity}_{action}_body_{locale}`  
Where `{entity}` is `client` or `provider`.

---

## Rider notifications (`NotificationTypeEnum`)

| Type | Title key | Body key | Placeholders | Default channels | Triggered by |
|------|-----------|----------|--------------|------------------|--------------|
| `otp_requested` | `title_otp_requested` | `body_otp_requested` | `:otp_code` | sms | `SendOtp` listener |
| `ride_started` | `title_ride_started` | `body_ride_started` | — | push, in_app | `RideFlowService::start()` |
| `ride_ended` | `title_ride_ended` | `body_ride_ended` | `:cost` | push, in_app | `RideFlowService` (after parking photo) |
| `low_balance` | `title_low_balance` | `body_low_balance` | — | push | `WalletService` (balance &lt; 10 SAR) |
| `balance_depleted_mid_ride` | `title_balance_depleted_mid_ride` | `body_balance_depleted_mid_ride` | — | push, in_app | `notifications:check-triggers` |
| `package_expiry_reminder` | `title_package_expiry_reminder` | `body_package_expiry_reminder` | `:package_name` | push | `notifications:check-triggers` (3 days before end) |
| `package_expired` | `title_package_expired` | `body_package_expired` | `:package_name` | push | `notifications:check-triggers` |
| `topup_success` | `title_topup_success` | `body_topup_success` | `:amount`, `:balance` | push | `WalletService` (charge) |
| `payment_failed` | `title_payment_failed` | `body_payment_failed` | — | push, in_app | `PaymentService` |
| `promo_offer` | `title_promo_offer` | `body_promo_offer` | `:promo_code`, `:discount` | push | (reserved) |
| `refund_approved` | `title_refund_approved` | `body_refund_approved` | `:amount` | push, in_app | (reserved) |
| `refund_rejected` | `title_refund_rejected` | `body_refund_rejected` | `:transaction_id` | push, in_app | (reserved) |
| `ticket_reply` | `title_ticket_reply` | `body_ticket_reply` | `:ticket_id` | push, in_app | (legacy — use `complain_status_changed`) |
| `complain_status_changed` | `title_complain_status_changed` | `body_complain_status_changed` | `:complain_number`, `:status`, `:ticket_id` | push, in_app | `ComplainService` (status update) |

---

## Provider notifications (`NotificationTypeEnum`)

| Type | Title key | Body key | Placeholders | Default channels | Triggered by |
|------|-----------|----------|--------------|------------------|--------------|
| `investor_verification_result` | `title_investor_verification_result` | `body_investor_verification_result` | `:status`, `:notes` | push, in_app | `ProviderService` (approve/reject) |
| `settlement_transferred` | `title_settlement_transferred` | `body_settlement_transferred` | `:amount`, `:month` | push, in_app | `SettlementService` |
| `settlement_invoice_ready` | `title_settlement_invoice_ready` | `body_settlement_invoice_ready` | `:month` | push, in_app | (reserved) |
| `settlement_objection_resolved` | `title_settlement_objection_resolved` | `body_settlement_objection_resolved` | `:month`, `:status` | push, in_app | (reserved) |

---

## Staff / dashboard alerts (`NotificationTypeEnum`)

| Type | Title key | Body key | Placeholders | Default channels | Triggered by |
|------|-----------|----------|--------------|------------------|--------------|
| `ride_over_duration` | `title_ride_over_duration` | `body_ride_over_duration` | `:ride_id`, `:client_name` | dashboard_alert | `notifications:check-triggers` (&gt; 1 hour) |
| `geofence_violation` | `title_geofence_violation` | `body_geofence_violation` | `:scooter_code` | dashboard_alert | (reserved) |
| `fleet_all_maintenance` | `title_fleet_all_maintenance` | `body_fleet_all_maintenance` | `:provider_name` | dashboard_alert | (reserved) |
| `vehicle_banned` | `title_vehicle_banned` | `body_vehicle_banned` | `:scooter_code` | dashboard_alert | (reserved) |
| `ticket_sla_breach` | `title_ticket_sla_breach` | `body_ticket_sla_breach` | `:ticket_id` | dashboard_alert | `notifications:check-triggers` (&gt; 24h) |
| `complain_created` | `title_complain_created` | `body_complain_created` | `:name`, `:complain_number`, `:title` | database (admin) | `ComplainService` (new complaint) |
| `campaign_broadcast` | `title_campaign_broadcast` | `body_campaign_broadcast` | `:campaign_title` | dashboard_alert | (reserved) |

---

## Wallet notifications (`GlobalNotification` + lang keys)

| Type / key | Title key | Body key | Placeholders | Triggered by |
|------------|-----------|----------|--------------|--------------|
| `wallet_charge` | `title_wallet_charge` | `body_wallet_charge` | `:amount` | `WalletTransactionObserver` (CHARGE) |
| `wallet_transfer_deposit` | `title_wallet_transfer_deposit` | `body_wallet_transfer_deposit` | `:amount` | `WalletTransactionObserver` (TRANSFER deposit) |
| `wallet_transfer_withdrawal` | `title_wallet_transfer_withdrawal` | `body_wallet_transfer_withdrawal` | `:amount`, `:name` | `WalletTransactionObserver` (TRANSFER withdrawal) |
| `wallet_cashback` | `title_wallet_cashback` | `body_wallet_cashback` | `:amount` | `WalletTransactionObserver` (CASHBACK) |
| `wallet_debt` | `title_wallet_debt` | `body_wallet_debt` | `:amount` | `Wallet` model observer |
| `wallet_withdraw` | `title_wallet_withdraw` | `body_wallet_withdraw` | `:amount` | (legacy key in lang) |
| `wallet_charge_request_rejected` | `title_wallet_charge_request_rejected` | `body_wallet_charge_request_rejected` | `:reason` | `WalletChargeRequest` model |
| `withdraw_accept` | `withdraw_accept_title` | `withdraw_accept_body` | `:amount` | (legacy) |
| `withdraw_reject` | `withdraw_reject_title` | `withdraw_reject_body` | — | (legacy) |

---

## Entity lifecycle (`NotificationTypeEnum` + `GlobalNotification`)

| Type | Title key pattern | Body key pattern | Placeholders | Triggered by |
|------|-------------------|------------------|--------------|--------------|
| `entity_created` | `{entity}_created_title_{locale}` | `{entity}_created_body_{locale}` | `:name`, `:id` | `SupplierObserver` → admins |
| `entity_approved` | `{entity}_approved_title_{locale}` | `{entity}_approved_body_{locale}` | `:id` | `SupplierObserver` → provider |
| `entity_rejected` | `{entity}_rejected_title_{locale}` | `{entity}_rejected_body_{locale}` | `:id`, `:reason` | `SupplierObserver` → provider |
| `entity_needs_approval` | `{entity}_needs_approval_title_{locale}` | `{entity}_needs_approval_body_{locale}` | `:id` | `SupplierObserver` → admins |

`{entity}` = `client` or `provider`.

### Entity profile update requests

| Stored type | Title key | Body key | Triggered by |
|-------------|-----------|----------|--------------|
| `entity_update_approved` | `title_entity_update_approved` | `body_entity_update_approved` | `EntityUpdate` model |
| `entity_update_rejected` | `title_entity_update_rejected` | `body_entity_update_rejected` | `EntityUpdate` model |

---

## Contact & support

| Type | Title key | Body key | Placeholders | Triggered by |
|------|-----------|----------|--------------|--------------|
| `contact_notify` | `new_contact` | `new_contact_us_Message_body` | `:name` | `ContactController` → admin |
| `contact_replied` | `title_contact_replied` | `body_contact_replied` | `:reply` | `Contact` model (admin reply) |

---

## Account moderation

| Stored type | Title key | Body key | Triggered by |
|-------------|-----------|----------|--------------|
| `blocked` | `title_block` | `body_block` | `SupplierObserver` (user blocked) |

---

## Settlement (legacy `GlobalNotification` keys)

| Type | Title key | Body key | Placeholders |
|------|-----------|----------|--------------|
| `settlement_accepted` | `title_settlement_accepted` | `body_settlement_accepted` | `:settlement_id` |
| `settlement_rejected` | `title_settlement_rejected` | `body_settlement_rejected` | `:settlement_id` |
| — | `title_create_settlement_data` | `body_create_settlement_data` | — |

### Locale-suffixed settlement keys (in lang files)

- `settlement_created_title_{en\|ar}` / `settlement_created_body_{en\|ar}` — `:settlement_number`
- `settlement_accepted_title_{en\|ar}` / `settlement_accepted_body_{en\|ar}` — `:settlement_number`
- `settlement_rejected_title_{en\|ar}` / `settlement_rejected_body_{en\|ar}` — `:settlement_number`, `:rejection_reason`

---

## Order notifications (legacy — from previous platform)

Lang keys exist for order flow; types are in `NotificationTypeEnum` but may not be used in BEEB scooter flows.

| Type | Title key (EN) | Body key (EN) | Placeholders |
|------|----------------|---------------|--------------|
| `order_created` | `order_created_title_en` | `order_created_body_en` | `:order_number` |
| `order_pending_approval` | `order_pending_approval_title_en` | `order_pending_approval_body_en` | `:order_number` |
| `order_accepted` | `order_accepted_title_en` | `order_accepted_body_en` | `:order_number` |
| `order_rejected` | `order_rejected_title_en` | `order_rejected_body_en` | `:order_number` |
| `order_pending_payment` | `order_pending_payment_title_en` | `order_pending_payment_body_en` | `:order_number` |
| `order_preparing` | `order_preparing_title_en` | `order_preparing_body_en` | `:order_number` |
| `order_delivered_to_delivery` | `order_delivered_to_delivery_title_en` | `order_delivered_to_delivery_body_en` | `:order_number` |
| `order_on_the_way` | `order_on_the_way_title_en` | `order_on_the_way_body_en` | `:order_number` |
| `order_completed` | `order_completed_title_en` | `order_completed_body_en` | `:order_number` |
| `order_cancelled` | `order_cancelled_title_en` | `order_cancelled_body_en` | `:order_number` |

Arabic equivalents use `_ar` suffix (e.g. `order_created_title_ar`).

Older flat keys also exist: `title_order_created`, `body_order_created` (`:order_num`), etc.

---

## Admin broadcast (no lang file keys)

| Type | Payload | Notes |
|------|---------|-------|
| `admin_notify` | `body_ar`, `body_en` (free text) | `AdminNotify` job, `NotifyUser`, manual admin send |
| `user_notify` | `title`, `body` (free text) | Custom user notification |

---

## Misc / legacy keys in lang files

| Key | Description |
|-----|-------------|
| `TestNotification` | Test string |
| `title_finish_order` / `body_finish_order` | `:order_num` |
| `title_admin_notify` | Admin notice title |
| `title_get_car` / `body_get_car` | `:plate`, `:order_id` |
| `title_entity_created` / `body_entity_created` | Generic entity created |
| `title_contact_notify` / `body_contact_notify` | `:name` |
| `merchant_*_{locale}` | Merchant keys (AR lang only; legacy) |

---

## `NotificationTypeEnum` — full constant list

```php
// Orders (legacy)
order_created, order_pending_approval, order_accepted, order_rejected,
order_pending_payment, order_completed, order_cancelled,
order_preparing, order_delivered_to_delivery, order_on_the_way

// Wallet
wallet_charge, wallet_transfer_deposit, wallet_transfer_withdraw,
wallet_transfer_withdrawal, wallet_withdraw, wallet_cashback,
wallet_debt, wallet_charge_request_rejected

// Contact
contact_replied, contact_notify

// Settlement
settlement_accepted, settlement_rejected

// Entity
entity_created, entity_approved, entity_rejected, entity_needs_approval

// Withdraw
withdraw_accept, withdraw_reject

// Rider (BEEB)
otp_requested, ride_started, ride_ended, low_balance,
balance_depleted_mid_ride, package_expiry_reminder, package_expired,
topup_success, payment_failed, promo_offer, refund_approved,
refund_rejected, ticket_reply

// Provider (BEEB)
investor_verification_result, settlement_transferred,
settlement_invoice_ready, settlement_objection_resolved

// Staff (BEEB)
ride_over_duration, geofence_violation, fleet_all_maintenance,
vehicle_banned, ticket_sla_breach, campaign_broadcast
```

---

## NotificationTypeEnum fallbacks

When `NotificationService` or `NotificationMessageTrait` resolves by `type` alone, keys follow `title_{type}` / `body_{type}` in both `en/notification.php` and `ar/notification.php`.

All constants in `NotificationTypeEnum` have these fallbacks, including:

- `entity_approved`, `entity_rejected`, `entity_needs_approval`
- `settlement_request` (mirrors `title_create_settlement_data` copy)
- `complain_created`, `complain_status_changed`

Entity-specific `GlobalNotification` keys (`provider_approved_title`, `client_created_body`, …) also have unsuffixed fallbacks in each locale file, in addition to `_en` / `_ar` suffixed keys.

---

## Adding a new notification

1. Add constant to `app/Enums/NotificationTypeEnum.php`.
2. Add `title_{type}` and `body_{type}` to both:
   - `resources/lang/en/notification.php`
   - `resources/lang/ar/notification.php`
3. If using `NotificationEvent`, register default channels in `NotificationService::$defaultChannels`.
4. Dispatch via `NotificationService::notify(new NotificationEvent(...))` or `GlobalNotification` with explicit keys.
5. Update this document.

---

## Scheduled trigger command

```bash
php artisan notifications:check-triggers
```

Checks: mid-ride balance depletion, package expiry reminder/expired, ride over duration, ticket SLA breach.
