# Payment & Dynamic QRIS Orchestration

## Overview
BoonTrack menyediakan infrastruktur pembayaran multi-provider yang berfokus pada **Dynamic QRIS** (Quick Response Code Indonesian Standard) per pesanan, memastikan settlement instan, rekonsiliasi deterministik, dan penanganan masa berlaku (*expiry*) otomatis.

---

## 1. Adapter Pattern Multi-Provider QRIS

Untuk mendukung fleksibilitas integrasi dengan berbagai payment gateway berlisensi Bank Indonesia (seperti Midtrans, Xendit, atau aggregator berlisensi lainnya) tanpa mengubah logika inti checkout, BoonTrack menggunakan **Payment Provider Adapter Pattern**.

```mermaid
classDiagram
    class PaymentProviderAdapter {
        <<interface>>
        +generate_dynamic_qris(order, tenant_credentials) QRISResult
        +verify_webhook_signature(headers, raw_body, secret) bool
        +check_transaction_status(transaction_id, tenant_credentials) StatusResult
        +cancel_payment(transaction_id, tenant_credentials) bool
    }

    class MidtransAdapter {
        +generate_dynamic_qris(...)
        +verify_webhook_signature(...)
        +check_transaction_status(...)
        +cancel_payment(...)
    }

    class XenditAdapter {
        +generate_dynamic_qris(...)
        +verify_webhook_signature(...)
        +check_transaction_status(...)
        +cancel_payment(...)
    }

    class PaymentOrchestrator {
        -adapter_registry: Dict
        +create_order_payment(order, tenant)
        +handle_callback(provider_name, request)
    }

    PaymentOrchestrator --> PaymentProviderAdapter
    PaymentProviderAdapter <|.. MidtransAdapter
    PaymentProviderAdapter <|.. XenditAdapter
```

### Karakteristik Adapter Interface
- **Dynamic QR Code Payload**: Setiap transaksi menghasilkan string payload QRIS unik berstandar ASPI / EMVCo dengan nominal tepat (termasuk kode unik jika diperlukan).
- **Tenant-Scoped Credentials**: API keys dan webhook secret disimpan terenkripsi per merchant (`tenant_id`), memungkinkan setiap merchant menggunakan akun payment gateway miliknya sendiri atau akun master platform.
- **Unified Status Mapping**: Menstandarkan status dari berbagai format gateway menjadi enum internal seragam: `PENDING`, `PAID`, `EXPIRED`, `FAILED`, `REFUNDED`.

---

## 2. Cron & Background Worker Rekonsiliasi Status

Meskipun webhook adalah jalur notifikasi utama, kegagalan jaringan atau downtime gateway dapat menyebabkan webhook hilang (*missed webhook*). Untuk menjamin integritas transaksi, BoonTrack menjalankan **Status Reconciliation Worker**.

```mermaid
sequenceDiagram
    autonumber
    participant Cron as Reconciliation Worker (Cron)
    participant DB as PostgreSQL (Orders)
    participant Adapter as Payment Adapter
    participant PGW as Payment Gateway API
    participant Outbox as Transactional Outbox

    loop Setiap 2 - 5 Menit
        Cron->>DB: SELECT * FROM orders WHERE status = 'PENDING' AND expires_at > NOW()
        loop Untuk Setiap Order PENDING
            Cron->>Adapter: check_transaction_status(gateway_ref_id)
            Adapter->>PGW: GET /v2/{order_id}/status (External API)
            PGW-->>Adapter: Return status (settlement / paid)
            
            alt Status Gateway = SETTLED / PAID
                Cron->>DB: UPDATE orders SET status = 'PAID', paid_at = NOW()
                Cron->>Outbox: INSERT outbox_events (event: order.paid)
                Note over DB,Outbox: Atomic Commit
            else Status Gateway = PENDING
                Cron->>Cron: Lewati, tunggu polling berikutnya
            end
        end
    end
```

### Strategi Rekonsiliasi
- **Polling Terjadwal**: Memeriksa seluruh transaksi berstatus `PENDING` yang masih dalam masa aktif.
- **Idempotent Update**: Menggunakan klausa `WHERE status = 'PENDING'` saat update status database untuk mencegah penimpaan status yang sudah diproses oleh webhook real-time.
- **Event Outbox Trigger**: Jika terdeteksi pelunasan via polling rekonsiliasi, event `order.paid` langsung dimasukkan ke tabel outbox untuk memicu notifikasi WhatsApp dan Meta CAPI secara otomatis.

---

## 3. Automated Expiry & Inventory Release Handling

Dynamic QRIS memiliki batas waktu pembayaran spesifik (default: 15 hingga 60 menit) untuk mencegah penumpukan inventaris yang tertahan (*stock locking*).

### Siklus Penanganan Kedaluwarsa (*Expiry Handling*)
1. **Timestamp Expiry Deterministik**: Saat order dibuat, field `expires_at` disimpan di database dan disinkronkan ke parameter gateway.
2. **Scheduled Sweeper**:
   - Worker berkala mengeksekusi query:
     ```sql
     UPDATE orders
     SET status = 'EXPIRED', updated_at = NOW()
     WHERE status = 'PENDING' 
       AND expires_at <= NOW()
     RETURNING id, tenant_id;
     ```
3. **Pelepasan Stok (Stock Release)**:
   - Untuk setiap order yang bertransisi ke `EXPIRED`, sistem mengembalikan kuantitas stok produk ke inventaris katalog secara otomatis.
4. **Pembatalan di Gateway**:
   - Adapter mengirimkan panggilan pembatalan (*cancel/expire*) ke gateway agar kode QRIS tidak dapat lagi dipindai oleh pelanggan setelah batas waktu terlewati.
5. **Notifikasi Pelanggan**:
   - Jika diaktifkan merchant, sistem mengirimkan pesan WhatsApp menginfokan bahwa sesi pembayaran telah berakhir dan memberikan tautan untuk checkout ulang jika berkenan.
