# 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: ` (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 `item[0].item_id` sebagai `order_id` untuk Midtrans (validasi format/unik per transaksi aktif). - 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/?sig=", "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 } ```