Midtrans-Middleware/server
Tengku Achmad 28ccc8001f docs: correct LOG_EXPOSE_API scope — does not gate /openapi.json or /docs
Those two routes are only gated by requireAdminAuth (Basic Auth), independent
of LOG_EXPOSE_API, which controls /api/logs* and the dev/test echo endpoints.
Previous wording implied all of /api/logs*, /openapi.json, and /docs shared
the same LOG_EXPOSE_API gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 23:18:55 +07:00
..
README.md docs: correct LOG_EXPOSE_API scope — does not gate /openapi.json or /docs 2026-08-03 23:18:55 +07:00
api-docs.html feat: add internal admin tooling — log viewer and Scalar API docs 2026-08-03 22:49:26 +07:00
index.cjs fix: default LOG_EXPOSE_API to closed (false) when unset 2026-08-03 23:06:36 +07:00
logviewer.html feat: add internal admin tooling — log viewer and Scalar API docs 2026-08-03 22:49:26 +07:00
openapi.json fix: scrub real customer PII and a captured webhook payload from docs 2026-08-03 23:01:16 +07:00

README.md

Simaya Midtrans Payment Server

Backend Express.js server untuk integrasi pembayaran Midtrans dengan sistem ERP.

Dokumentasi endpoint yang selalu sinkron dengan kode: buka /docs (Scalar, HTTP Basic Auth) atau /openapi.json. Untuk testing manual, import postman/Midtrans-Middleware.postman_collection.json — mencakup seluruh route di bawah ini.

Daftar Isi

Fitur Utama

1. Dual Mode Payment

  • CORE API: Bank Transfer, Credit Card, GoPay/QRIS, Convenience Store
  • SNAP: Hosted payment interface dengan UI Midtrans
  • Dua bentuk payload: langsung (order_id/nominal/customer/expire_at) atau ERP (mercant_id/item[])
  • Signature HMAC-SHA256 pada token, TTL configurable
  • order_id yang mengandung karakter selain A-Za-z0-9-_~. (mis. :) otomatis disanitasi jadi . sebelum dikirim ke Midtrans

3. ERP Integration

  • Notifikasi otomatis ke sistem ERP setelah pembayaran sukses
  • Multi-endpoint support (comma-separated URLs)
  • Signature verification untuk keamanan

4. Webhook Handler

  • Unified webhook untuk CORE dan SNAP
  • Signature verification
  • Idempotent notification handling

5. State Persisten

  • activeOrders, notifiedOrders, orderRetryCount, orderMerchantId disimpan ke server/data/state.json (auto-save), jadi tidak hilang saat server restart

6. Advanced Logging

  • Level-based logging (debug, info, warn, error), file harian (LOGS_DDMMYYYY.log)
  • Auto-delete log lebih tua dari LOG_RETENTION_DAYS
  • Log viewer interaktif di /api/logs/view (search, filter level, click-to-trace)
  • Payload masking untuk sensitive data, Jakarta timezone (WIB/UTC+7)

Konfigurasi Environment

Referensi lengkap ada di .env.example di root project. Ringkasan per kategori:

Midtrans Configuration

MIDTRANS_SERVER_KEY=your-server-key
MIDTRANS_CLIENT_KEY=your-client-key
MIDTRANS_IS_PRODUCTION=false

ENABLE_BANK_TRANSFER=true
ENABLE_CREDIT_CARD=true
ENABLE_GOPAY=true
ENABLE_CSTORE=true
EXTERNAL_API_KEY=your-api-key            # X-API-KEY untuk /createtransaksi & POST /api/payment-links
PAYMENT_LINK_SECRET=your-signing-secret  # HMAC secret untuk token link
PAYMENT_LINK_TTL_MINUTES=1440
PAYMENT_LINK_BASE=http://localhost:5174/pay

ERP Integration

ERP_NOTIFICATION_URL=https://your-erp.com/api/payment-notification
ERP_NOTIFICATION_URLS=https://erp1.com/api/notif,https://erp2.com/api/notif  # multi-URL, opsional
ERP_CLIENT_ID=your-erp-client-id         # juga dipakai sebagai secret HMAC (fallback ERP_CLIENT_SECRET)
ERP_ENABLE_NOTIF=true

Logging & Admin Access

LOG_LEVEL=info
LOG_EXPOSE_API=false            # default sudah tertutup (false) kalau tidak di-set
LOG_BASIC_AUTH_USER=            # wajib diisi di production untuk lindungi /api/logs* dan /docs
LOG_BASIC_AUTH_PASS=
LOG_RETENTION_DAYS=30
LOG_BUFFER_SIZE=1000

CORS & Rate Limiting

CORS_ALLOWED_ORIGINS=https://your-frontend.example.com   # kosong = izinkan semua origin (dev default)
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX=20

Server

PORT=8000
NODE_ENV=production   # HARUS "production" di server production — banyak gerbang keamanan bergantung pada ini

API Endpoints

Health & Config

GET /api/health

Health check endpoint.

GET /api/config

Ambil payment toggles & Midtrans client key saat ini.

POST /api/config (non-production only)

Update payment toggles saat runtime. Return 403 kalau NODE_ENV=production.

Payment Operations

POST /api/payments/charge

Buat transaksi Core API (bank transfer, card, GoPay/QRIS, cstore). Dipanggil langsung dari browser checkout, tanpa perlu pra-registrasi order. Rate-limited.

POST /api/payments/snap/token

Generate Snap token untuk hosted payment popup. Dipanggil langsung dari browser (PayPage/CheckoutPage). Rate-limited.

GET /api/payments/:orderId/status

Cek status transaksi (passthrough Midtrans). Juga memicu fallback notifikasi ERP kalau status sudah sukses tapi belum ter-notify.

POST /api/payment-links — payload langsung

Requires X-API-KEY. Rate-limited.

{
  "order_id": "DEMO-MERCHANT-0001:ITEM-0001",
  "nominal": 179000,
  "customer": { "name": "Budi Santoso", "phone": "081234567890", "email": "budi.santoso@example.com" },
  "expire_at": 1785852063058
}

order_id dipakai apa adanya untuk tracking internal; versi yang dikirim ke Midtrans disanitasi (: dan karakter lain → .). expire_at dipakai kalau valid & di masa depan, kalau tidak fallback ke PAYMENT_LINK_TTL_MINUTES.

Response:

{ "status": "200", "messages": "SUCCESS", "data": { "url": "...", "order_id": "...", "midtrans_order_id": "...", "expire_at": 1785852063058 } }

POST /createtransaksi — payload ERP (mercant_id/item)

Requires X-API-KEY. Rate-limited.

{
  "mercant_id": "merchant-001",
  "nominal": 150000,
  "nama": "John Doe",
  "email": "john@example.com",
  "no_telepon": "081234567890",
  "item": [{ "item_id": "product-123", "nama": "Product Name", "harga": 150000, "qty": 1 }],
  "allowed_methods": ["bank_transfer", "gopay"]
}

order_id diturunkan sebagai mercant_id.item[0].item_id (dot-joined). expire_at TIDAK bisa di-supply client — dihitung server dari PAYMENT_LINK_TTL_MINUTES.

Response:

{ "status": "200", "messages": "SUCCESS", "data": { "url": "http://localhost:5174/pay/eyJ2Ijox..." } }

GET /api/payment-links/:token

Resolve token jadi detail order: { order_id, nominal, customer, expire_at, allowed_methods }.

Webhook

POST /api/payments/notification

Handler webhook Midtrans (unified CORE & SNAP). Verifikasi signature_key (SHA512(order_id+status_code+gross_amount+MIDTRANS_SERVER_KEY)), lalu meneruskan notifikasi ke ERP. Return non-2xx kalau notifikasi ERP gagal supaya Midtrans retry.

Logs & Docs (internal — HTTP Basic Auth di production)

Endpoint Keterangan
GET /api/logs Log in-memory terbaru (?limit, ?level, ?q)
GET /api/logs/files Daftar file log harian
GET /api/logs/files/:filename Isi satu file log (JSON), auto-redirect ke viewer HTML kalau diakses dari browser
GET /api/logs/view?file=... Viewer interaktif: filter level, cari teks, klik nilai apa pun untuk trace
GET /openapi.json Spec OpenAPI mentah
GET /docs Dokumentasi API interaktif (Scalar)

Dev/Test (aktif hanya kalau LOG_EXPOSE_API=true)

  • POST /api/echo, POST /api/echo2 — mock endpoint untuk testing callback ERP
  • POST /api/test/notify-erp — trigger notifyERP() langsung, bypass webhook

Payment Flow

ERP System → POST /createtransaksi atau POST /api/payment-links → Server generate token → Payment URL

2. Payment Execution Flow

Customer → Payment URL → Frontend resolve token →
Pilih metode → POST /api/payments/charge atau /api/payments/snap/token → Midtrans

3. Payment Completion Flow

Midtrans → Webhook → Verifikasi signature → Update ledger →
Notify ERP → Tandai order selesai

4. Format Notifikasi ke ERP

{
  "mercant_id": "merchant-001",
  "status_code": "200",
  "nominal": "150000",
  "signature": "sha512-hash"
}

Perhitungan Signature: SHA512(mercant_id + status_code + nominal + ERP_CLIENT_ID)

Catatan: payload ke ERP tidak membawa order_id — pencocokan transaksi murni lewat mercant_id. Kalau satu mercant_id bisa punya lebih dari satu order aktif bersamaan, ERP perlu strategi lain untuk membedakannya.

Testing

Lihat folder tests/ untuk file-file testing:

node tests/test-create-payment-link.cjs
node tests/test-frontend-payload.cjs
node tests/test-snap-token.cjs

Atau pakai postman/Midtrans-Middleware.postman_collection.json — mencakup seluruh endpoint di atas, dikelompokkan per folder, termasuk request untuk mensimulasikan webhook Midtrans dari payload asli.

Logging

Log Levels

  • debug: Detail untuk diagnosa masalah
  • info: Informasi umum
  • warn: Peringatan
  • error: Error yang masih memungkinkan aplikasi jalan

Format Log (file, satu baris per event)

[2026-08-03T21:35:54.746+07:00] [INFO ] webhook.notifying_erp | {"order_id":"DEMO-MERCHANT-0002.ITEM-0002","mercant_id":"DEMO-MERCHANT-0002"}

Paling nyaman dibaca lewat GET /api/logs/view (filter level, cari teks, klik nilai untuk trace) daripada membaca file mentah.

Event Penting

Payment Lifecycle: charge.request, charge.success, charge.error, status.request, webhook.received

Payment Link: payment-links.create.issued, createtransaksi.issued, paymentlink.resolve.success

ERP Integration: erp.notify.start, erp.notify.success, erp.notify.error, erp.notify.skip

Security: webhook.signature.invalid, createtransaksi.unauthorized, payment-links.create.unauthorized, admin.auth.failed, rate_limit.blocked

Keamanan

  1. Signature Verification — webhook, token payment link, notifikasi ERP semuanya ditandatangani/diverifikasi.
  2. Idempotency — cegah duplikasi order/notifikasi ERP; state disimpan konsisten pakai order_id yang sudah disanitasi (Midtrans-safe) di semua map internal (activeOrders, notifiedOrders, orderRetryCount, orderMerchantId), sehingga tidak ada mismatch walau order_id asli mengandung karakter seperti :.
  3. API Key AuthenticationX-API-KEY untuk /createtransaksi dan POST /api/payment-links. Dev mode fallback (bypass) hanya aktif kalau NODE_ENV !== 'production' dan key belum di-set.
  4. HTTP Basic Auth untuk admin tooling/api/logs*, /openapi.json, /docs semuanya dilindungi requireAdminAuth (LOG_BASIC_AUTH_USER/PASS). Wajib di-set di production — gerbang ini bergantung pada NODE_ENV=production untuk benar-benar dipaksa (lihat poin 8). Terpisah dari itu, LOG_EXPOSE_API=false (default) mematikan /api/logs* dengan 403 sekalipun auth-nya lolos, dan membuat endpoint dev/test (/api/echo, /api/echo2, /api/test/notify-erp) tidak terdaftar sama sekali — flag ini tidak memengaruhi /openapi.json atau /docs.
  5. Rate Limiting/createtransaksi, /api/payment-links, /api/payments/charge, /api/payments/snap/token dibatasi per-IP (RATE_LIMIT_MAX request per RATE_LIMIT_WINDOW_MS) untuk mencegah bot abuse / card-testing pada endpoint yang memang harus tetap bisa diakses langsung dari browser tanpa API key.
  6. CORS — dibatasi lewat CORS_ALLOWED_ORIGINS (comma-separated). Kosong = izinkan semua origin (default dev).
  7. Payload Masking — field sensitif (card number, CVV, token, server key) otomatis disamarkan di log.
  8. NODE_ENV production — banyak gerbang keamanan (API key, Basic Auth) bergantung pada isDevEnv(). Pastikan proses production (ecosystem.config.cjs) benar-benar set NODE_ENV=production, lalu pm2 restart <app> --update-env setelah mengubahnya.

Running the Server

npm install
npm run server        # atau: node server/index.cjs
# Server jalan di http://localhost:8000

Common Issues

"Transaction already pending"

Penyebab: Order ID sudah punya transaksi pending di Midtrans. Solusi: Pakai instruksi pembayaran yang ada, atau buat order baru dengan ID berbeda.

"ERP notification failed"

Penyebab: Endpoint ERP tidak bisa diakses, atau signature mismatch (ERP_CLIENT_ID beda dengan yang dipakai ERP). Solusi: Cek ERP_NOTIFICATION_URL(S) dan ERP_CLIENT_ID, replay payload webhook asli lewat request "Simulate Midtrans Notification (Local)" di Postman collection untuk debug.

"Invalid signature on webhook"

Penyebab: MIDTRANS_SERVER_KEY salah, atau webhook dari sumber tak sah. Solusi: Pastikan MIDTRANS_SERVER_KEY sama dengan akun Midtrans yang memproses transaksi tersebut (sandbox vs production beda key).

Log//docs mengembalikan 401 atau 503

Penyebab: LOG_BASIC_AUTH_USER/PASS belum di-set di production (NODE_ENV=production). Solusi: Set kedua env var tersebut, restart server.

Support