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

8.5 KiB
Raw Blame History

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):
    {
      "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):
    {
      "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).
  • 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:
    {
      "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:
    {
      "status_code": "200",
      "status_message": "OK",
      "url": "https://cifopayment.id/pay/eyJvcmRlcl9pZCI6IklURU0tMTIzNDUiLCJub21pbmFsIjoyMDAwMDAsImV4cCI6MTczMDAwMDAwMH0?sig=4c7f...",
      "exp": 1730000000
    }