146 lines
8.5 KiB
Markdown
146 lines
8.5 KiB
Markdown
# 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 (HMAC‑SHA256) dengan TTL dan anti‑replay.
|
||
- 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 anti‑replay.
|
||
- Arsitektur UI (`docs/ui-architecture.md`): Tambah rute `"/pay/:token"`, alur token‑driven. `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/anti‑replay, 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 anti‑replay 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:
|
||
- Rate‑limit `POST /createtransaksi` dan audit log.
|
||
- Anti‑replay: 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.
|
||
|
||
## Non‑Functional 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 HMAC‑SHA256.
|
||
- Kinerja: endpoint create/resolver respon <200ms p95 (lokal dev).
|
||
|
||
## Risks & Mitigations
|
||
- Replay/penyalahgunaan link: enforce TTL, anti‑replay flag, rate‑limit.
|
||
- 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 token‑driven flow.
|
||
|
||
## Timeline & Tasking (5 hari)
|
||
- Day 1: Skeleton endpoint `createtransaksi` + HMAC signing + env wiring.
|
||
- Day 2: Resolver API + idempotensi + anti‑replay 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: HMAC‑SHA256 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
|
||
}
|
||
``` |