Midtrans-Middleware/docs/stories/midtrans-e2e-checkout-to-we...

183 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 nonMidtrans (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.