Midtrans-Middleware/docs/sprint-change-proposal.md

146 lines
8.5 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.

# Sprint Change Proposal — ERP → Create Transaction → Payment Link Flow
## Summary
- Trigger: Checkout tidak lagi dimulai dari tombol “Buy Now” di frontend. Sistem eksternal (ERP/billing script) mengirim data transaksi ke server dan menerima tautan pembayaran terenkripsi untuk dibagikan ke pengguna.
- Outcome: Backend menjadi titik inisiasi transaksi. Pengguna membuka Payment Page via link (`/pay/:token`), memilih metode (VA/GoPay/Cstore/Kartu), lalu UI mengeksekusi charge seperti biasa. Webhook Midtrans dan ERP callback tetap berjalan.
## Objectives (Sprint)
- Menyediakan endpoint backend `POST /createtransaksi` yang mengeluarkan payment link bertanda-tangan (HMACSHA256) dengan TTL dan antireplay.
- Menambahkan rute frontend `"/pay/:token"` (Payment Page) untuk memvalidasi token, menampilkan metode, dan melakukan charge menggunakan `order_id` dari token.
- Menjaga kompatibilitas endpoint yang ada: `POST /api/payments/charge`, `GET /api/payments/:orderId/status`, `POST /api/payments/webhook`.
- Menyelaraskan PRD, Arsitektur UI, dan Story E2E dengan alur baru.
## Scope & Impact
- PRD (`docs/prd.md`): Tambah FR “External Create Transaction & Payment Link”, ubah sumber `order_id` (berasal dari `item_id`), tambahkan ketentuan TTL, signature, dan antireplay.
- Arsitektur UI (`docs/ui-architecture.md`): Tambah rute `"/pay/:token"`, alur tokendriven. `CheckoutPage` tetap sebagai demo/QA.
- Story E2E (`docs/stories/midtrans-e2e-checkout-to-webhook.md`): Mulai dari Payment Page via token, bukan dari CheckoutPage langsung.
- Backend (`server/index.cjs`): Tambah endpoint `POST /createtransaksi` dan resolver token (API untuk FE). Env baru: `EXTERNAL_API_KEY`, `PAYMENT_LINK_SECRET`, `PAYMENT_LINK_TTL_MINUTES`.
- Frontend: Tambah halaman `PayPage` (`/pay/:token`), service untuk resolve token. `postCharge/getPaymentStatus` tetap.
## Proposed Design
### 1) Backend — Create Transaction API
- Endpoint: `POST /createtransaksi`
- Auth: Header `X-API-KEY: <EXTERNAL_API_KEY>` (validasi exact match; opsi IP whitelist & rate limit di reverse proxy).
- Body (contoh minimal):
```json
{
"merchant_id": "TKG-250520029803",
"deskripsi": "Pembelian item",
"nominal": 200000,
"nama": "Dwiki Kurnia Sandi",
"no_telepon": "081234567890",
"email": "demo@example.com",
"item": [
{ "item_id": "ITEM-12345", "qty": 1, "price": 200000, "name": "Produk A" }
]
}
```
- Mapping `order_id`: gunakan `"mercant_id:item_id"` bila keduanya tersedia (contoh: `MERC-001:ITEM-12345`). Jika salah satu tidak ada, fallback ke `item[0].item_id` atau `mercant_id`. Tujuan: item yang sama pada merchant berbeda tetap menghasilkan order unik sehingga link bisa diterbitkan.
- Idempotensi: jika `order_id` + `nominal` sama dan status transaksi masih `pending`, kembalikan payment link sebelumnya; jika `nominal` berbeda, kembalikan `422 AMOUNT_MISMATCH`.
- Response (sukses):
```json
{
"status_code": "200",
"status_message": "OK",
"url": "https://cifopayment.id/pay/<token>?sig=<signature>",
"exp": 1730000000
}
```
- `token`: opaque base64url berisi klaim minimal (mis. `order_id`, `nominal`, `exp`) yang disimpan server.
- `signature`: `hex(HMAC_SHA256(token, PAYMENT_LINK_SECRET))`.
- `exp`: UNIX epoch seconds (TTL default 30 menit; dapat dikonfigurasi).
### 2) Backend — Payment Link Resolve API
- Endpoint: `GET /api/payment-links/:token` (digunakan frontend untuk bootstrap Payment Page).
- Query: otomatis memverifikasi signature (`sig` di query atau header), TTL/antireplay, dan mengembalikan payload:
```json
{
"order_id": "ITEM-12345",
"nominal": 200000,
"customer": { "name": "Dwiki", "phone": "081234567890", "email": "demo@example.com" },
"expire_at": 1730000000,
"allowed_methods": ["bank_transfer", "gopay", "cstore", "credit_card"]
}
```
- Error: `401 INVALID_SIGNATURE`, `410 LINK_EXPIRED`, `409 LINK_USED` (opsional jika antireplay menandai sekali pakai).
### 3) Frontend — Payment Page (`/pay/:token`)
- Flow:
- Ambil `token` dari URL → panggil `GET /api/payment-links/:token`.
- Set lokal `orderId`, `amount`, `expireAt`, dan info pelanggan.
- Render komponen metode (VA/GoPay/Cstore/Kartu) seperti di Checkout, tetapi `orderId` berasal dari token.
- Setelah charge, navigasi ke `"/payments/:orderId/status"` (polling status tetap).
- Catatan: `CheckoutPage` tetap ada untuk demo/QA; jalur produksi menggunakan Payment Page via link.
### 4) Webhook & ERP Callback
- Tetap: `POST /api/payments/webhook` memverifikasi signature Midtrans dan memperbarui status.
- ERP Callback: kirim `POST` ke `https://apibackend.erpskrip.id/paymentnotification/` pada status sukses, signature `sha512(mercant_id + status_code + nominal + client_id)` seperti implementasi saat ini.
### 5) Security & Config
- Secrets:
- `EXTERNAL_API_KEY`: memvalidasi `X-API-KEY` dari ERP.
- `PAYMENT_LINK_SECRET`: kunci HMAC untuk signature token.
- `PAYMENT_LINK_TTL_MINUTES`: default 30.
- Praktik:
- Ratelimit `POST /createtransaksi` dan audit log.
- Antireplay: tandai token sebagai “used” setelah berhasil charge (opsional; atau izinkan reuse sampai status final).
- Rotasi secret terjadwal; invalidasi token lama secara bertahap bila diperlukan.
## Acceptance Criteria (Sprint)
- Backend mengeluarkan payment link dengan `token` + `signature`, valid hingga TTL.
- Resolve API mengembalikan `order_id` dan `nominal` yang konsisten; invalid jika signature/TTL gagal.
- Frontend Payment Page (`/pay/:token`) dapat:
- Memvalidasi token dan menampilkan metode pembayaran.
- Melakukan charge via endpoint yang ada menggunakan `order_id` dari token.
- Menavigasi ke halaman status dan menampilkan detail (VA/QR/payment code/kartu) sesuai metode.
- Webhook menerima notifikasi Midtrans dan ERP callback tetap terkirim saat sukses.
- Dokumentasi PRD, Arsitektur UI, dan Story E2E diperbarui mencerminkan alur baru.
## NonFunctional Requirements
- Observability: logging request ID, event penting (`link.create`, `link.resolve`, `charge.start/success`, `webhook.receive`).
- Idempotensi: kembalikan link lama untuk transaksi `pending` dengan `order_id` + `nominal` yang sama.
- Keamanan: tidak mengekspos server key; signature Midtrans diverifikasi; token link ditandatangani HMACSHA256.
- Kinerja: endpoint create/resolver respon <200ms p95 (lokal dev).
## Risks & Mitigations
- Replay/penyalahgunaan link: enforce TTL, antireplay flag, ratelimit.
- Konflik `order_id`: validasi unik; strategy untuk recurring (suffix waktu/sequence bila diperlukan).
- Ketidaksesuaian nominal: `422 AMOUNT_MISMATCH` untuk `order_id` sama namun nominal beda.
- Gangguan ERP: retry callback dengan backoff, feature flag `ERP_ENABLE_NOTIF` untuk mematikan sementara.
## Rollback Plan
- Nonaktifkan konsumsi resolver token (feature flag) dan kembalikan ke alur Checkout demo.
- Pertahankan endpoint status & webhook agar UI tetap dapat polling status.
## Deliverables (Sprint)
- Backend: `POST /createtransaksi`, `GET /api/payment-links/:token` (validator signature/TTL), konfigurasi env baru.
- Frontend: halaman `PayPage` (`/pay/:token`) dengan integrasi panel metode yang ada.
- Docs: update PRD, Arsitektur UI, dan Story E2E sesuai tokendriven flow.
## Timeline & Tasking (5 hari)
- Day 1: Skeleton endpoint `createtransaksi` + HMAC signing + env wiring.
- Day 2: Resolver API + idempotensi + antireplay dasar.
- Day 3: FE `PayPage` + service `getPaymentLinkPayload(token)` + navigasi.
- Day 4: QA manual (sandbox) + webhook/ERP callback sanity.
- Day 5: Dokumentasi & polish (error codes, logging, runbook).
## Decisions Confirmed
- TTL payment link: 30 menit (konfigurabel; default 30) disetujui.
- Token: HMACSHA256 signature (tanpa enkripsi payload; confidentiality tidak diwajibkan saat ini).
- Recurring transaksi: tidak didukung. Kebijakan:
- Satu transaksi aktif per `item_id` pada satu waktu.
- Jika status sukses (`settlement` atau `capture + fraud_status=accept`), permintaan baru untuk `item_id` yang sama ditolak (`409 ORDER_COMPLETED`).
- Re-attempt diperbolehkan hanya jika transaksi sebelumnya tidak sukses dan sudah berakhir (`expire/cancel/deny`), menggunakan kebijakan unik `order_id` sesuai kebutuhan implementasi Midtrans.
## Appendix — Example
- Request `POST /createtransaksi` (ringkas): lihat contoh pada bagian Backend di atas.
- Response sukses:
```json
{
"status_code": "200",
"status_message": "OK",
"url": "https://cifopayment.id/pay/eyJvcmRlcl9pZCI6IklURU0tMTIzNDUiLCJub21pbmFsIjoyMDAwMDAsImV4cCI6MTczMDAwMDAwMH0?sig=4c7f...",
"exp": 1730000000
}
```