# 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
  }
  ```