# Webhook Handling — Pakasir

## Temuan penting (menyimpang dari asumsi awal README bagian 8.2)

Dokumentasi resmi Pakasir (https://pakasir.com/p/docs, per 3 Jul 2026) mengonfirmasi
webhook body TIDAK memiliki field signature/HMAC apa pun:

    {
      "amount": 22000,
      "order_id": "240910HDE7C9",
      "project": "depodomain",
      "status": "completed",
      "payment_method": "qris",
      "completed_at": "2024-09-10T08:07:02.819+07:00"
    }

Tidak ada mekanisme signature untuk divalidasi. Pakasir secara eksplisit merekomendasikan
verifikasi balik lewat Transaction Detail API sebagai sumber kebenaran, bukan payload
webhook mentah.

## Pendekatan yang dipakai

1. Webhook diterima -> simpan raw payload ke `payment_events` apa adanya (tetap sesuai
   README, untuk audit trail).
2. TIDAK ada pengecekan signature (karena tidak tersedia). Kolom yang semula direncanakan
   `signature_valid` diganti/ditambah menjadi `verified_via_callback` (boolean).
3. Sebelum mengubah status order, sistem WAJIB memanggil balik:
   `GET https://app.pakasir.com/api/transactiondetail?project={slug}&amount={amount}&order_id={order_id}&api_key={api_key}`
   dan mencocokkan `status`, `amount`, `order_id` terhadap data order internal
   (`price_at_order`, dsb) sebelum transisi PENDING_PAYMENT -> PAID.
4. Jika callback verification gagal (network error, mismatch, response tidak sesuai),
   payment_events dicatat dengan `verified_via_callback = false` dan TIDAK memicu
   fulfillment. Order tetap PENDING_PAYMENT, akan diambil ulang oleh reconciliation cron
   (bagian 6 README) yang juga melakukan reconciliation aktif ke API yang sama.
5. Idempotency tetap dicek berdasarkan `order_id` + `payment_reference`/status yang sudah
   pernah diproses sebelum callback verification dijalankan, supaya tidak spam API Pakasir
   untuk webhook duplikat yang jelas-jelas sudah diproses.

## Sandbox mode untuk testing

Project Pakasir punya toggle mode Sandbox di dashboard. Selama Sandbox aktif, tersedia:

- `POST /api/paymentsimulation` — simulasi pembayaran sukses tanpa QRIS asli, dipakai
  untuk test alur webhook end-to-end di lokal.
- `POST /api/transactioncancel` — membatalkan transaksi test.

`PAKASIR_SANDBOX_MODE` di `.env` (dibaca lewat `config('services.pakasir.sandbox_mode')`)
sekarang benar-benar dipakai di kode (`App\Services\PakasirClient`), berbeda dari catatan
awal di atas: saat bernilai `true`, `PakasirClient` **tidak memanggil API Pakasir sama
sekali** — baik `createTransaction()` maupun `getTransactionDetail()`. Ini dipilih karena
project belum tentu sudah punya akun/project Pakasir asli terdaftar selama masih fase
development, jadi tidak bisa mengandalkan `/api/paymentsimulation` milik Pakasir (butuh
project & api key asli). Untuk simulasi "pembayaran sukses" versi lokal murni, pakai:

```bash
php artisan pakasir:simulate-payment {order_public_token}
```

Command ini (lihat `App\Console\Commands\SimulatePakasirPayment`, ditolak kalau
`APP_ENV=production`) menjalankan transisi status + fulfillment yang sama persis dengan
`PakasirWebhookController::handle()` setelah verifikasi berhasil, jadi behaviour yang
diuji representatif terhadap alur produksi nanti — hanya jalur verifikasinya yang
dilewati.

## Perubahan sumber kebenaran amount (Fase 10 — fee QRIS buyer/seller)

Sejak fitur fee QRIS ditanggung buyer/seller ditambahkan (lihat `docs/CHANGELOG.md`
Fase 10), amount yang dicocokkan di `PakasirWebhookController` dan
`ReconcileOrders::reconcilePendingPayments()` adalah **`orders.qris_request_amount`**,
BUKAN `orders.price_at_order` lagi. Alasannya: kalau produk di-setting `fee_payer=seller`,
amount yang dikirim ke Pakasir saat `transactioncreate` sengaja dihitung mundur (lebih
kecil dari `price_at_order`) supaya total yang dibayar buyer (`amount + fee`) tetap pas
dengan harga produk. Kalau verifikasi tetap dicocokkan ke `price_at_order`, transaksi
`fee_payer=seller` tidak akan pernah terverifikasi. Untuk `fee_payer=buyer`,
`qris_request_amount` sama persis dengan `price_at_order`, jadi behaviour lama tidak
berubah untuk kasus ini.
