# 04. Payment Driver dan Vendor Adapter Engine

## 1. Abstraksi Vendor Payment Gateway

PEWE mengadopsi **Adapter Design Pattern** untuk mengisolasi perbedaan teknis antar vendor pembayaran (Bank Direct, Xendit, Midtrans). Dengan abstraksi ini, controller aplikasi PEWE tidak perlu mengetahui detail endpoint, struktur payload JSON, atau mekanisme autentikasi dari masing-masing vendor.

### Diagram Kelas Arsitektur Driver Payment Gateway (Mermaid Class Diagram)

```mermaid
classDiagram
    class PaymentGatewayInterface {
        <<interface>>
        +createPaymentRequest(PaymentRequest $paymentRequest, array $requestBody) void
        +getWebhookExternalId(ServerRequestInterface $request) string
        +parseWebhookStatus(ServerRequestInterface $request, string $configJson) string
        +cancelPaymentRequest(PaymentRequest $paymentRequest, string $configJson) bool
        +updateVa(PaymentRequest $paymentRequest, array $updateData, string $configJson) bool
        +createQris(PaymentRequest $paymentRequest, string $configJson) void
        +getPaymentRequest(PaymentRequest $paymentRequest, string $configJson) array
    }

    class PaymentGatewayFactory {
        +make(string $provider)$ PaymentGatewayInterface
    }

    class HttpClient {
        +post(string $url, array $data, array $headers) array
        +get(string $url, array $headers) array
        +put(string $url, array $data, array $headers) array
    }

    class XenditAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
        +cancelPaymentRequest()
    }

    class MidtransAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
        +cancelPaymentRequest()
    }

    class BniDirectAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
    }

    class BriDirectAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
    }

    class BsiDirectAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
    }

    class MandiriDirectAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
    }

    class MuamalatDirectAdapter {
        +createPaymentRequest()
        +parseWebhookStatus()
    }

    PaymentGatewayFactory ..> PaymentGatewayInterface : Instantiates
    PaymentGatewayInterface <|.. XenditAdapter : Implements
    PaymentGatewayInterface <|.. MidtransAdapter : Implements
    PaymentGatewayInterface <|.. BniDirectAdapter : Implements
    PaymentGatewayInterface <|.. BriDirectAdapter : Implements
    PaymentGatewayInterface <|.. BsiDirectAdapter : Implements
    PaymentGatewayInterface <|.. MandiriDirectAdapter : Implements
    PaymentGatewayInterface <|.. MuamalatDirectAdapter : Implements
    XenditAdapter --> HttpClient : Uses
    MidtransAdapter --> HttpClient : Uses
```

---

## 2. Matriks Komparasi Vendor Payment Gateway & Bank H2H API

| Fitur / Parameter | Xendit (v3) | Midtrans | BRI Direct H2H | BNI Direct H2H | Mandiri Direct | BSI Direct H2H | Muamalat H2H |
|:---|:---|:---|:---|:---|:---|:---|:---|
| **Payment Request (Multi-VA)** | ✅ API v3 Payment Requests | ✅ Snap API | ❌ | ❌ | ❌ | ❌ | ❌ |
| **VA Bank Spesifik** | ✅ BCA, BNI, BRI, Mandiri, Permata | ✅ Core API | ✅ BRIVA | ✅ e-Collection | ✅ Mandiri VA | ✅ BSI VA | ✅ Muamalat VA |
| **QRIS Dynamic** | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ Terbatas | ⚠️ Terbatas |
| **Webhook Pelunasan** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Expired Otomatis** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Cancel Payment Request** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Biaya per Transaksi** | ~Rp 4.000-4.500 | ~Rp 4.000-4.500 | ~Rp 1.000-2.500 | ~Rp 1.000-2.500 | ~Rp 1.000-2.500 | ~Rp 1.500-3.000 | ~Rp 1.500-3.000 |

### Catatan Kapabilitas Vendor
- **Xendit (v3)**: Menggunakan API modern v3 (`/payment_requests`) berbasis unified payment method. Mendukung Virtual Account, QRIS, E-Wallet, serta direct debit.
- **Midtrans**: Menggunakan Core API dan Snap API. Otentikasi menggunakan HTTP Basic Auth dengan Server Key encoded Base64.
- **Bank Direct (H2H)**: Koneksi langsung Host-to-Host tanpa aggregator pihak ketiga. Menghasilkan potongan biaya (*merchant fee*) yang lebih murah, namun memerlukan manajemen signature HMAC spesifik per bank.

---

## 3. Kontrak Interface (`PaymentGatewayInterface`)

Setiap driver vendor wajib mengimplementasikan interface **`App\Payment\PaymentGatewayInterface`**:

```php
namespace App\Payment;

use App\Entity\PaymentRequest;
use Psr\Http\Message\ServerRequestInterface;

interface PaymentGatewayInterface
{
    /**
     * Executes the API call to the provider and populates the Payment Request object
     * with payment specific information like VA number or Payment URL.
     */
    public function createPaymentRequest(PaymentRequest $paymentRequest, array $requestBody): void;

    /**
     * Extracts the external_id (our Payment Request reference) from the incoming webhook request.
     */
    public function getWebhookExternalId(ServerRequestInterface $request): ?string;

    /**
     * Validates the webhook payload using the channel config and returns the new status
     * (e.g. 'paid', 'expired') or null if it should be ignored/invalid.
     */
    public function parseWebhookStatus(ServerRequestInterface $request, string $configJson): ?string;

    /**
     * Cancels an existing payment request / VA on the provider.
     */
    public function cancelPaymentRequest(PaymentRequest $paymentRequest, string $configJson): bool;

    /**
     * Updates an existing VA on the provider.
     */
    public function updateVa(PaymentRequest $paymentRequest, array $updateData, string $configJson): bool;

    /**
     * Generates a QRIS string for the payment request.
     */
    public function createQris(PaymentRequest $paymentRequest, string $configJson): void;

    /**
     * Gets the latest payment request or VA details from the provider.
     */
    public function getPaymentRequest(PaymentRequest $paymentRequest, string $configJson): ?array;
}
```

---

## 4. Instansiasi Driver via `PaymentGatewayFactory`

Pemilihan dan pembuatan objek driver dilakukan secara dinamis oleh **`App\Payment\PaymentGatewayFactory`** berdasarkan nilai kolom `channel.provider`:

```php
namespace App\Payment;

use App\Payment\Driver\XenditAdapter;
use App\Payment\Driver\MidtransAdapter;
use App\Payment\Driver\BriDirectAdapter;
use App\Payment\Driver\BniDirectAdapter;
use App\Payment\Driver\MandiriDirectAdapter;
use App\Payment\Driver\BsiDirectAdapter;
use App\Payment\Driver\MuamalatDirectAdapter;
use InvalidArgumentException;
use function strtolower;
use function str_contains;

class PaymentGatewayFactory
{
    public static function make(string $provider): PaymentGatewayInterface
    {
        $provider = strtolower($provider);

        if (str_contains($provider, 'xendit')) {
            return new XenditAdapter();
        }
        if (str_contains($provider, 'midtrans')) {
            return new MidtransAdapter();
        }
        if (str_contains($provider, 'bri')) {
            return new BriDirectAdapter();
        }
        if (str_contains($provider, 'bni')) {
            return new BniDirectAdapter();
        }
        if (str_contains($provider, 'mandiri')) {
            return new MandiriDirectAdapter();
        }
        if (str_contains($provider, 'bsi')) {
            return new BsiDirectAdapter();
        }
        if (str_contains($provider, 'muamalat')) {
            return new MuamalatDirectAdapter();
        }

        throw new InvalidArgumentException("Unsupported payment provider: {$provider}");
    }
}
```

---

## 5. Rincian Pemanggilan Vendor Specific & HttpClient Resilience

### 5.1. Driver Xendit API v3 (`XenditAdapter`)
PEWE menggunakan **API Reference v3 (Payment Requests)** terbaru dari Xendit (`POST /payment_requests`).

- **Header Khusus**: `api-version: 2022-07-31` (Wajib untuk v3).
- **Autentikasi**: HTTP Basic Auth menggunakan `api_key` Xendit sebagai Username.
- **Pembatalan**: Memanggil `POST /payment_requests/{payment_request_id}/cancel`.
- **Handling Signature**: Menguji header `webhook-signature` HMAC-SHA256 dari payload menggunakan Webhook Secret Xendit.

### 5.2. Direct Bank Adapters (`BniDirectAdapter`, `BriDirectAdapter`, `BsiDirectAdapter`, `MandiriDirectAdapter`, `MuamalatDirectAdapter`)
Untuk integrasi Host-to-Host (H2H) Bank:
- **BNI / BRI / Mandiri Direct**: Menggunakan protokol H2H bank untuk penciptaan dan pembatalan VA.
- **Bank Muamalat Direct**: Menggunakan alur khusus inquiry & payment callback melalui `App\Controller\Api\V1\MuamalatController`.

### 5.3. Komponen HTTP Client Resilien (`App\Payment\HttpClient`)
Seluruh driver menggunakan wrapper `HttpClient` terpusat yang menjamin ketahanan koneksi (*network resilience*):
- **Timeout Management**: Batas waktu koneksi (*connection timeout*) set 10 detik dan total timeout request 30 detik.
- **Automatic Audit Logging**: Setiap request keluar (*outbound request*) dan response masuk dari server vendor dicatat secara otomatis ke tabel `transaction_log` (`event = vendor_request` & `vendor_response`).
- **SSL Certificate Verification**: Menjamin seluruh komunikasi menggunakan HTTPS dengan verifikasi sertifikat SSL aktif di lingkungan produksi.

---

## 6. Engine Perhitungan Biaya Channel (Fee Calculation)

Setiap transaksi yang melalui channel tertentu akan dihitung biayanya berdasarkan kolom `fee_flat` dan `fee_percent` pada tabel `channel`:

$$\text{Total Fee} = \text{fee\_flat} + \left( \text{amount} \times \frac{\text{fee\_percent}}{100} \right)$$

Contoh konfigurasi channel:
- **`xendit_va_bni`**: `fee_flat = 4000.00`, `fee_percent = 0.0000` $\rightarrow$ Fee = Rp 4.000
- **`xendit_qris`**: `fee_flat = 0.00`, `fee_percent = 0.0070` (0.7%) $\rightarrow$ Fee 0.7% dari nominal transaksi.

### Simulasi Tabel Perhitungan Biaya per Nominal Transaksi

| Channel Code | Nominal Transaksi (`amount`) | `fee_flat` | `fee_percent` | Kalkulasi Fee | Total Fee Terpotong (`fee`) | Net Diterima Merchant |
|:---|:---|:---|:---|:---|:---|:---|
| `xendit_va_bni` | Rp 50.000 | Rp 4.000 | 0.00% | $4000 + (50000 \times 0)$ | Rp 4.000 | Rp 46.000 |
| `xendit_va_bni` | Rp 1.000.000 | Rp 4.000 | 0.00% | $4000 + (1000000 \times 0)$ | Rp 4.000 | Rp 996.000 |
| `xendit_qris` | Rp 50.000 | Rp 0 | 0.70% | $0 + (50000 \times 0.007)$ | Rp 350 | Rp 49.650 |
| `xendit_qris` | Rp 1.000.000 | Rp 0 | 0.70% | $0 + (1000000 \times 0.007)$ | Rp 7.000 | Rp 993.000 |
| `direct_va_bri` | Rp 500.000 | Rp 1.500 | 0.00% | $1500 + (500000 \times 0)$ | Rp 1.500 | Rp 498.500 |

Biaya ini dicatat pada kolom `payment_request.fee` untuk keperluan audit dan rekonsiliasi keuangan.
