Midtrans-Middleware/docs/stories/midtrans-e2e-checkout-to-we...

10 KiB
Raw Blame History

Story: End-to-End Midtrans — Checkout → Webhook (Payment Sukses)

User Story

Sebagai pembeli, Saya ingin menyelesaikan pembayaran melalui Midtrans dan melihat status pembayaran otomatis diperbarui melalui webhook, Sehingga saya mendapatkan konfirmasi yang jelas dan pesanan saya segera diproses.

Story Context

Integrates with:

  • Frontend: src/pages/CheckoutPage.tsx, src/features/payments/components/* (BankTransferPanel, CardPanel, GoPayPanel, CStorePanel), src/pages/PaymentStatusPage.tsx, hooks usePaymentStatus, navigasi usePaymentNavigation
  • Backend: server/index.cjs — endpoint POST /api/payments/charge, GET /api/payments/:orderId/status, penambahan POST /api/payments/webhook
  • Services: src/services/api.ts (postCharge, getPaymentStatus)

Technology: React + Vite, TanStack Query (polling status), Express, midtrans-client Core API, Midtrans 3DS (kartu).

Follows pattern: Semua panggilan ke Midtrans dilakukan via backend; UI menavigasi ke halaman status (/payments/:orderId/status) dan melakukan polling hingga status final; webhook memperbarui status di backend sebagai sumber kebenaran utama.

Flow Overview

  1. Checkout
  • Pengguna memilih metode: bank_transfer (VA/echannel), gopay (QRIS/GoPay), cstore (Alfamart/Indomaret), credit_card (tokenisasi + 3DS).
  • UI menampilkan panel sesuai metode dan mengumpulkan input yang diperlukan.
  1. Charge (Backend)
  • Frontend memanggil POST /api/payments/charge via postCharge(payload).
  • Backend meneruskan ke Midtrans core.charge(payload) dan mengembalikan respons (termasuk order_id, transaction_status, serta redirect_url untuk kartu jika 3DS diperlukan).
  1. 3DS (Kartu)
  • Bila respons berisi redirect_url, UI memanggil authenticate3ds(redirect_url) untuk challenge 3DS.
  • Setelah 3DS selesai, status akan menjadi capture (berhasil) atau status lain sesuai penilaian fraud.
  1. Status Page + Polling
  • UI menavigasi ke /payments/:orderId/status (opsional: ?m=<method>).
  • Halaman melakukan polling GET /api/payments/:orderId/status setiap 3 detik sampai status final: settlement, capture, expire, cancel, deny, refund, chargeback.
  1. Webhook (Notifikasi Midtrans)
  • Backend menyediakan POST /api/payments/webhook untuk menerima notifikasi transaksi.
  • Backend memverifikasi signature_key dan memperbarui status transaksi (DB/in-memory) sebagai sumber kebenaran.
  • UI tetap polling hingga menangkap status final (atau dapat diinformasikan melalui SSE jika ditambahkan kemudian).
  1. Success Outcome
  • Untuk bank_transfer/gopay/cstore: status settlement dianggap sukses.
  • Untuk credit_card: status capture dengan fraud_status=accept dianggap sukses.
  • UI menampilkan badge hijau “Pembayaran berhasil” dan detail metode (VA, QR links, masked card, dsb.).
  1. ERP Notification (External Callback)
  • Ketika pembayaran sukses, backend mengirim callback ke ERP pada URL https://apibackend.erpskrip.id/paymentnotification/.
  • Metode: POST, Body JSON berisi:
    • data: { channel, nominal, mercant_id, customer_name }
    • status_code: selalu "200" untuk pembayaran sukses
    • signature: sha512(mercant_id + status_code + nominal + client_id) dalam hex lowercase
  • client_id disediakan oleh konfigurasi backend (contoh env ERP_CLIENT_ID).

Acceptance Criteria

  • Checkout
    • Pengguna dapat memilih metode Midtrans dan melihat panel input/instruksi yang sesuai.
    • Tombol “Bayar” mengirim charge ke backend dan menampilkan feedback loading/error yang ramah.
  • Charge & 3DS
    • credit_card: tokenisasi via 3DS SDK; jika wajib 3DS, UI mengarahkan ke challenge lalu kembali ke halaman status.
    • bank_transfer/gopay/cstore: charge mengembalikan detail VA/QR/payment code sesuai; UI menampilkan instruksi yang relevan.
  • Status Page
    • Halaman status menampilkan order_id, metode, dan badge status: pending (kuning), settlement/capture (hijau), deny/cancel/expire/refund/chargeback (merah).
    • Polling berhenti otomatis ketika status final.
  • Webhook Backend
    • Endpoint POST /api/payments/webhook tersedia dan tervalidasi signature Midtrans.
    • Status transaksi diperbarui idempoten (repeat notification tidak merusak data), menyimpan metadata minimal: source=webhook, occurred_at.
    • Logging mencatat event webhook, hasil verifikasi, dan perubahan status.
  • Success Case
    • Setelah pembayaran sukses (settlement/capture), UI menampilkan konfirmasi “Pembayaran berhasil” dan menyediakan navigasi “Lihat Riwayat” dan “Kembali ke Checkout”.
  • ERP Notification
    • Saat status sukses (VA/GoPay/QRIS/Cstore = settlement, Kartu = capture + fraud_status=accept), backend melakukan POST ke https://apibackend.erpskrip.id/paymentnotification/ dengan body:
      • data: { channel: <string>, nominal: <number>, mercant_id: <string>, customer_name: <string> }
      • status_code: "200"
      • signature: hasil sha512(mercant_id + status_code + nominal + client_id)
    • Signature tervalidasi di sisi ERP (nilai harus cocok dengan rumus).
    • Callback idempoten (jika dipanggil ulang karena retry, tidak menggandakan efek di ERP).
  • Keamanan & Ketahanan
    • Server Key tidak terekspos ke frontend.
    • Verifikasi signature sesuai rumus Midtrans; input sensitif (token_id, card data) tidak tercatat di log.
    • Fallback polling tetap bekerja jika webhook terlambat.

Technical Notes

  • Endpoint backend saat ini:

    • POST /api/payments/charge — sudah ada (pass-through ke Midtrans Core API)
    • GET /api/payments/:orderId/status — sudah ada (memanggil core.transaction.status(orderId)).
    • Tambahkan: POST /api/payments/webhook — menerima notifikasi Midtrans.
  • Verifikasi Signature Midtrans (HTTP Notification)

    • signature_key = sha512(order_id + status_code + gross_amount + serverKey) (hex lowercase)
    • Contoh Node.js:
      const crypto = require('crypto')
      const signature = crypto
        .createHash('sha512')
        .update(orderId + statusCode + grossAmount + serverKey)
        .digest('hex')
      const isValid = signature === req.body.signature_key
      
  • Status Mapping (UI)

    • Final: settlement, capture, expire, cancel, deny, refund, chargeback.
    • Sukses: settlement (VA/QR/Cstore), capture + fraud_status=accept (Card).
    • Normalisasi di src/features/payments/lib/midtrans.ts melalui normalizeMidtransStatus.
  • ERP External Notification (Backend → ERP)

    • Kirim callback hanya ketika status sukses:
      • VA/GoPay/QRIS/Cstore: transaction_status === 'settlement'
      • Kartu: transaction_status === 'capture' dan fraud_status === 'accept'
    • Payload contoh:
      {
        "data": {
          "channel": "DANA",
          "nominal": 200000,
          "mercant_id": "TKG-250520029803",
          "customer_name": "Dwiki Kurnia Sandi"
        },
        "status_code": "200",
        "signature": "<hex sha512>"
      }
      
    • Perhitungan signature (Node.js):
      const crypto = require('crypto')
      const mercantId = data.mercant_id
      const nominal = String(data.nominal)
      const statusCode = '200'
      const clientId = process.env.ERP_CLIENT_ID || ''
      const raw = `${mercantId}${statusCode}${nominal}${clientId}`
      const signature = crypto.createHash('sha512').update(raw).digest('hex')
      
    • Pengiriman (contoh menggunakan fetch):
      const res = await fetch('https://apibackend.erpskrip.id/paymentnotification/', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ data, status_code: '200', signature })
      })
      if (!res.ok) throw new Error(`ERP notify failed: ${res.status}`)
      
    • Idempotensi: gunakan kunci unik (mis. order_id) untuk memastikan sekali proses pada ERP; lakukan retry dengan backoff jika gagal.

Test Cases (Sandbox)

  • Credit Card (3DS)
    • Input kartu sandbox → tokenisasi → challenge 3DS → kembali ke status → capture + fraud_status=accept → UI sukses.
  • Bank Transfer (VA Permata/Mandiri E-Channel)
    • Charge menghasilkan VA → UI menampilkan VA dan bank → simulasi pembayaran → webhook atau polling → settlement → UI sukses.
  • GoPay/QRIS
    • Charge menghasilkan actions (deeplink/QR) → buka tautan → settlement → UI sukses.
    • Backend mengirim ERP callback dengan status_code="200" dan signature valid.
  • Negative/Edge
    • Tidak bayar dalam waktu batas → expire → UI merah, polling berhenti.
    • deny/cancel → UI merah; riwayat mencatat status akhir.
    • ERP callback tidak dikirim untuk status non-sukses.

Dependencies & Config

  • Env Frontend: VITE_API_BASE_URL, VITE_MIDTRANS_CLIENT_KEY, VITE_MIDTRANS_ENV (sandbox/production).
  • Env Backend: MIDTRANS_SERVER_KEY, MIDTRANS_CLIENT_KEY (opsional untuk log), MIDTRANS_IS_PRODUCTION.
  • Toggle fitur: GET/POST /api/config (bank_transfer, credit_card, gopay, cstore). Pastikan metode yang digunakan aktif.
  • ERP Notifikasi:
    • ERP_NOTIFICATION_URL="https://apibackend.erpskrip.id/paymentnotification/"
    • ERP_CLIENT_ID="<dari ERP>"
    • Opsional: ERP_ENABLE_NOTIF=true (feature flag untuk mengaktifkan/nonaktifkan callback)

Validation Checklist

  • Perubahan dapat selesai dalam satu sesi (dokumen + endpoint webhook kecil)
  • Integrasi mengikuti pola yang ada (postCharge, getPaymentStatus, polling)
  • Tidak ada kebutuhan arsitektur baru besar
  • Acceptance criteria dapat diuji di sandbox
  • Rollback sederhana: nonaktifkan webhook, andalkan polling sementara
  • ERP callback dikirim pada status sukses dengan status_code="200" dan signature sesuai

Rollback & Risk

  • Rollback: nonaktifkan konsumsi webhook (feature flag), gunakan polling status sementara.
  • Risiko: signature salah, keterlambatan webhook, idempotensi tidak benar. Mitigasi: verifikasi ketat, logging, dan retry aman.
  • Risiko ERP: endpoint tidak tersedia atau timeouts. Mitigasi: retry dengan backoff, dead-letter queue (opsional), observabilitas.

Out of Scope

  • Metode eksternal nonMidtrans (contoh: cPay/CIFO Token) tidak termasuk dalam skenario sukses Midtrans ini.
  • Refund/chargeback flow admin; hanya ditampilkan sebagai status jika terjadi.

Notes

  • Rujuk docs/integration-midtrans.md untuk detail kartu/3DS.
  • Tambahkan dokumentasi runbook QA untuk simulasi webhook dan verifikasi status manual.