# Story: End-to-End Midtrans — Checkout → Webhook (Payment Sukses)

## User Story
Sebagai pembeli,
Saya ingin menyelesaikan pembayaran melalui Midtrans dan melihat status pembayaran otomatis diperbarui melalui webhook,
Sehingga saya mendapatkan konfirmasi yang jelas dan pesanan saya segera diproses.

## Story Context
**Integrates with:**
- Frontend: `src/pages/CheckoutPage.tsx`, `src/features/payments/components/*` (BankTransferPanel, CardPanel, GoPayPanel, CStorePanel), `src/pages/PaymentStatusPage.tsx`, hooks `usePaymentStatus`, navigasi `usePaymentNavigation`
- Backend: `server/index.cjs` — endpoint `POST /api/payments/charge`, `GET /api/payments/:orderId/status`, penambahan `POST /api/payments/webhook`
- Services: `src/services/api.ts` (`postCharge`, `getPaymentStatus`)

**Technology:** React + Vite, TanStack Query (polling status), Express, `midtrans-client` Core API, Midtrans 3DS (kartu).

**Follows pattern:** Semua panggilan ke Midtrans dilakukan via backend; UI menavigasi ke halaman status (`/payments/:orderId/status`) dan melakukan polling hingga status final; webhook memperbarui status di backend sebagai sumber kebenaran utama.

## Flow Overview
1) Checkout
- Pengguna memilih metode: `bank_transfer` (VA/echannel), `gopay` (QRIS/GoPay), `cstore` (Alfamart/Indomaret), `credit_card` (tokenisasi + 3DS).
- UI menampilkan panel sesuai metode dan mengumpulkan input yang diperlukan.

2) Charge (Backend)
- Frontend memanggil `POST /api/payments/charge` via `postCharge(payload)`.
- Backend meneruskan ke Midtrans `core.charge(payload)` dan mengembalikan respons (termasuk `order_id`, `transaction_status`, serta `redirect_url` untuk kartu jika 3DS diperlukan).

3) 3DS (Kartu)
- Bila respons berisi `redirect_url`, UI memanggil `authenticate3ds(redirect_url)` untuk challenge 3DS.
- Setelah 3DS selesai, status akan menjadi `capture` (berhasil) atau status lain sesuai penilaian fraud.

4) Status Page + Polling
- UI menavigasi ke `/payments/:orderId/status` (opsional: `?m=<method>`).
- Halaman melakukan polling `GET /api/payments/:orderId/status` setiap 3 detik sampai status final: `settlement`, `capture`, `expire`, `cancel`, `deny`, `refund`, `chargeback`.

5) Webhook (Notifikasi Midtrans)
- Backend menyediakan `POST /api/payments/webhook` untuk menerima notifikasi transaksi.
- Backend memverifikasi `signature_key` dan memperbarui status transaksi (DB/in-memory) sebagai sumber kebenaran.
- UI tetap polling hingga menangkap status final (atau dapat diinformasikan melalui SSE jika ditambahkan kemudian).

6) Success Outcome
- Untuk `bank_transfer/gopay/cstore`: status `settlement` dianggap sukses.
- Untuk `credit_card`: status `capture` dengan `fraud_status=accept` dianggap sukses.
- UI menampilkan badge hijau “Pembayaran berhasil” dan detail metode (VA, QR links, masked card, dsb.).

7) ERP Notification (External Callback)
- Ketika pembayaran sukses, backend mengirim callback ke ERP pada URL `https://apibackend.erpskrip.id/paymentnotification/`.
- Metode: `POST`, Body JSON berisi:
  - `data`: `{ channel, nominal, mercant_id, customer_name }`
  - `status_code`: selalu `"200"` untuk pembayaran sukses
  - `signature`: `sha512(mercant_id + status_code + nominal + client_id)` dalam hex lowercase
- `client_id` disediakan oleh konfigurasi backend (contoh env `ERP_CLIENT_ID`).

## Acceptance Criteria
- Checkout
  - Pengguna dapat memilih metode Midtrans dan melihat panel input/instruksi yang sesuai.
  - Tombol “Bayar” mengirim charge ke backend dan menampilkan feedback loading/error yang ramah.
- Charge & 3DS
  - `credit_card`: tokenisasi via 3DS SDK; jika wajib 3DS, UI mengarahkan ke challenge lalu kembali ke halaman status.
  - `bank_transfer/gopay/cstore`: charge mengembalikan detail VA/QR/payment code sesuai; UI menampilkan instruksi yang relevan.
- Status Page
  - Halaman status menampilkan `order_id`, metode, dan badge status: `pending` (kuning), `settlement/capture` (hijau), `deny/cancel/expire/refund/chargeback` (merah).
  - Polling berhenti otomatis ketika status final.
- Webhook Backend
  - Endpoint `POST /api/payments/webhook` tersedia dan tervalidasi signature Midtrans.
  - Status transaksi diperbarui idempoten (repeat notification tidak merusak data), menyimpan metadata minimal: `source=webhook`, `occurred_at`.
  - Logging mencatat event webhook, hasil verifikasi, dan perubahan status.
- Success Case
  - Setelah pembayaran sukses (settlement/capture), UI menampilkan konfirmasi “Pembayaran berhasil” dan menyediakan navigasi “Lihat Riwayat” dan “Kembali ke Checkout”.
- ERP Notification
  - Saat status sukses (VA/GoPay/QRIS/Cstore = `settlement`, Kartu = `capture` + `fraud_status=accept`), backend melakukan `POST` ke `https://apibackend.erpskrip.id/paymentnotification/` dengan body:
    - `data`: `{ channel: <string>, nominal: <number>, mercant_id: <string>, customer_name: <string> }`
    - `status_code`: `"200"`
    - `signature`: hasil `sha512(mercant_id + status_code + nominal + client_id)`
  - Signature tervalidasi di sisi ERP (nilai harus cocok dengan rumus).
  - Callback idempoten (jika dipanggil ulang karena retry, tidak menggandakan efek di ERP).
- Keamanan & Ketahanan
  - Server Key tidak terekspos ke frontend.
  - Verifikasi signature sesuai rumus Midtrans; input sensitif (token_id, card data) tidak tercatat di log.
  - Fallback polling tetap bekerja jika webhook terlambat.

## Technical Notes
- Endpoint backend saat ini:
  - `POST /api/payments/charge` — sudah ada (pass-through ke Midtrans Core API)
  - `GET /api/payments/:orderId/status` — sudah ada (memanggil `core.transaction.status(orderId)`).
  - Tambahkan: `POST /api/payments/webhook` — menerima notifikasi Midtrans.

- Verifikasi Signature Midtrans (HTTP Notification)
  - `signature_key = sha512(order_id + status_code + gross_amount + serverKey)` (hex lowercase)
  - Contoh Node.js:
    ```js
    const crypto = require('crypto')
    const signature = crypto
      .createHash('sha512')
      .update(orderId + statusCode + grossAmount + serverKey)
      .digest('hex')
    const isValid = signature === req.body.signature_key
    ```

- Status Mapping (UI)
  - Final: `settlement`, `capture`, `expire`, `cancel`, `deny`, `refund`, `chargeback`.
  - Sukses: `settlement` (VA/QR/Cstore), `capture` + `fraud_status=accept` (Card).
  - Normalisasi di `src/features/payments/lib/midtrans.ts` melalui `normalizeMidtransStatus`.

- ERP External Notification (Backend → ERP)
  - Kirim callback hanya ketika status sukses:
    - VA/GoPay/QRIS/Cstore: `transaction_status === 'settlement'`
    - Kartu: `transaction_status === 'capture'` dan `fraud_status === 'accept'`
  - Payload contoh:
    ```json
    {
      "data": {
        "channel": "DANA",
        "nominal": 200000,
        "mercant_id": "TKG-250520029803",
        "customer_name": "Dwiki Kurnia Sandi"
      },
      "status_code": "200",
      "signature": "<hex sha512>"
    }
    ```
  - Perhitungan signature (Node.js):
    ```js
    const crypto = require('crypto')
    const mercantId = data.mercant_id
    const nominal = String(data.nominal)
    const statusCode = '200'
    const clientId = process.env.ERP_CLIENT_ID || ''
    const raw = `${mercantId}${statusCode}${nominal}${clientId}`
    const signature = crypto.createHash('sha512').update(raw).digest('hex')
    ```
  - Pengiriman (contoh menggunakan fetch):
    ```js
    const res = await fetch('https://apibackend.erpskrip.id/paymentnotification/', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ data, status_code: '200', signature })
    })
    if (!res.ok) throw new Error(`ERP notify failed: ${res.status}`)
    ```
  - Idempotensi: gunakan kunci unik (mis. `order_id`) untuk memastikan sekali proses pada ERP; lakukan retry dengan backoff jika gagal.

## Test Cases (Sandbox)
- Credit Card (3DS)
  - Input kartu sandbox → tokenisasi → challenge 3DS → kembali ke status → `capture` + `fraud_status=accept` → UI sukses.
- Bank Transfer (VA Permata/Mandiri E-Channel)
  - Charge menghasilkan VA → UI menampilkan VA dan bank → simulasi pembayaran → webhook atau polling → `settlement` → UI sukses.
- GoPay/QRIS
  - Charge menghasilkan `actions` (deeplink/QR) → buka tautan → `settlement` → UI sukses.
  - Backend mengirim ERP callback dengan `status_code="200"` dan signature valid.
- Negative/Edge
  - Tidak bayar dalam waktu batas → `expire` → UI merah, polling berhenti.
  - `deny/cancel` → UI merah; riwayat mencatat status akhir.
  - ERP callback tidak dikirim untuk status non-sukses.

## Dependencies & Config
- Env Frontend: `VITE_API_BASE_URL`, `VITE_MIDTRANS_CLIENT_KEY`, `VITE_MIDTRANS_ENV` (sandbox/production).
- Env Backend: `MIDTRANS_SERVER_KEY`, `MIDTRANS_CLIENT_KEY` (opsional untuk log), `MIDTRANS_IS_PRODUCTION`.
- Toggle fitur: `GET/POST /api/config` (bank_transfer, credit_card, gopay, cstore). Pastikan metode yang digunakan aktif.
- ERP Notifikasi:
  - `ERP_NOTIFICATION_URL="https://apibackend.erpskrip.id/paymentnotification/"`
  - `ERP_CLIENT_ID="<dari ERP>"`
  - Opsional: `ERP_ENABLE_NOTIF=true` (feature flag untuk mengaktifkan/nonaktifkan callback)

## Validation Checklist
- [ ] Perubahan dapat selesai dalam satu sesi (dokumen + endpoint webhook kecil)
- [ ] Integrasi mengikuti pola yang ada (`postCharge`, `getPaymentStatus`, polling)
- [ ] Tidak ada kebutuhan arsitektur baru besar
- [ ] Acceptance criteria dapat diuji di sandbox
- [ ] Rollback sederhana: nonaktifkan webhook, andalkan polling sementara
- [ ] ERP callback dikirim pada status sukses dengan `status_code="200"` dan signature sesuai

## Rollback & Risk
- Rollback: nonaktifkan konsumsi webhook (feature flag), gunakan polling status sementara.
- Risiko: signature salah, keterlambatan webhook, idempotensi tidak benar. Mitigasi: verifikasi ketat, logging, dan retry aman.
- Risiko ERP: endpoint tidak tersedia atau timeouts. Mitigasi: retry dengan backoff, dead-letter queue (opsional), observabilitas.

## Out of Scope
- Metode eksternal non‑Midtrans (contoh: cPay/CIFO Token) tidak termasuk dalam skenario sukses Midtrans ini.
- Refund/chargeback flow admin; hanya ditampilkan sebagai status jika terjadi.

## Notes
- Rujuk `docs/integration-midtrans.md` untuk detail kartu/3DS.
- Tambahkan dokumentasi runbook QA untuk simulasi webhook dan verifikasi status manual.