# Payment Callback Hub

Centralized webhook router untuk SingaPay & Faspay yang melayani banyak website (multi-tenant) berdasarkan **prefix** di `bill_no`/`reff_no`.

## Cara Kerja

```
SingaPay / Faspay
        │
        ▼  (1 webhook URL terdaftar)
https://hub.example.com/webhook/{provider}
        │  → simpan WebhookEvent → return 200 instan
        ▼
   Queue: ForwardWebhookJob
        │  → lookup mapping by prefix → forward HTTP POST
        └─ exponential retry (10s → 10m, 5x default)
```

## Setup

```bash
composer install
cp .env.example .env
php artisan key:generate

# Edit .env (DB_CONNECTION, QUEUE_CONNECTION=database, dll)

php artisan migrate
php artisan db:seed   # buat admin default: admin@hub.local / password

# Jalankan worker (penting!)
php artisan queue:work --tries=6
```

**Login admin**: `/login` → `admin@hub.local` / `password` (ganti via Profile).

## Endpoint Public (untuk gateway)

| Method | Path | Keterangan |
|--------|------|------------|
| POST | `/webhook/singapay` | Webhook handler SingaPay |
| POST | `/webhook/faspay` | Webhook handler Faspay |

Daftarkan URL ini di dashboard SingaPay/Faspay sebagai callback notifikasi.

## Endpoint Admin

| Path | Keterangan |
|------|------------|
| `/admin` | Dashboard + stats 24h |
| `/admin/mappings` | CRUD mapping (prefix → forward URL) |
| `/admin/events` | List webhook events (filter, search) |
| `/admin/events/{id}` | Detail event + tombol retry |

## Cara Menambahkan Website Baru

### 1. Di sisi Hub (admin panel)
- Buka `/admin/mappings` → **New Mapping**
- Isi:
  - **Name**: `Survey Center`
  - **Prefix**: `SC` (uppercase, A-Z 0-9)
  - **Provider**: `Both` atau pilih spesifik
  - **Forward URL**: `https://surveycenter.co.id/api/hub-webhook/singapay`
  - **Auth Type**: `HMAC SHA256`
  - **Secret**: random string panjang (di-encrypt di DB)
- Klik **Save** → klik **Test** untuk verifikasi konektivitas

### 2. Di sisi Website Konsumer

**a. Pakai prefix unik** saat create invoice gateway:
```php
$billNo = 'SC-123-20260430120000';
```

**b. Endpoint penerima** dengan verify signature:
```php
Route::post('/api/hub-webhook/singapay', function (Request $request) {
    $rawBody = $request->getContent();
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, env('HUB_SECRET'));

    if (!hash_equals($expected, $request->header('X-Hub-Signature', ''))) {
        return response()->json(['error' => 'invalid signature'], 401);
    }

    return app(SingaPayService::class)->webhook($request);
})->withoutMiddleware([VerifyCsrfToken::class]);
```

**c. ENV website konsumer**:
```env
HUB_SECRET=<secret-yang-sama-dengan-mapping-di-hub>
```

## Headers yang Dikirim Hub

| Header | Value |
|--------|-------|
| `X-Hub-Provider` | `singapay` / `faspay` |
| `X-Hub-Reff-No` | reff_no/bill_no asli |
| `X-Hub-Event-Id` | ID event di hub |
| `X-Hub-Attempt` | Nomor attempt (1, 2, ...) |
| `X-Hub-Signature` | `sha256=<hex>` (jika auth_type=hmac) |
| `X-Hub-Api-Key` | secret (jika auth_type=api_key) |

## Prefix Parsing

1. **Hyphen-based**: `SC-123-20260430` → prefix = `SC`
2. **Legacy alpha**: `RC65F3A1B2` → prefix = `RC`

## Retry Strategy

Backoff: **10s → 30s → 60s → 180s → 600s** (default `max_retries=5`).
Sukses jika HTTP 2xx. Lainnya retry sampai habis → status `failed`. Admin bisa retry manual via UI.

## Production Tips

- Pakai Redis queue (lebih cepat)
- Supervisor untuk `queue:work`:
  ```ini
  [program:hub-worker]
  command=php /path/artisan queue:work --tries=6 --timeout=60
  autorestart=true
  numprocs=2
  ```
- Health check: `GET /up`
- IP whitelist gateway → domain hub saja

## Troubleshooting

| Masalah | Solusi |
|---------|--------|
| Status `unmatched` | Prefix di payload tidak ada mapping aktif |
| HTTP 401 di forward | Secret hub ≠ secret website konsumer |
| Forward timeout | Naikkan `timeout_seconds` di mapping |
| Queue stuck | Cek `queue:work` running, lihat `failed_jobs` |
