parseEnable()'s generic fallback (return true for an unset var) is correct for the payment-method toggles it's shared with, but wrong for a security gate: LOG_EXPOSE_API controlled whether /api/logs*, /openapi.json, and /docs serve anything at all, and previously defaulted to true (open) when the env var was absent, contradicting what .env.example already documented as the safe default. Now explicitly defaults to 'false'. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| README.md | ||
| api-docs.html | ||
| index.cjs | ||
| logviewer.html | ||
| openapi.json | ||
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, importpostman/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
2. Payment Link Generation
- Dua bentuk payload: langsung (
order_id/nominal/customer/expire_at) atau ERP (mercant_id/item[]) - Signature HMAC-SHA256 pada token, TTL configurable
order_idyang mengandung karakter selainA-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,orderMerchantIddisimpan keserver/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
Payment Link & External API
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.
Payment Link
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 ERPPOST /api/test/notify-erp— triggernotifyERP()langsung, bypass webhook
Payment Flow
1. Payment Link Creation 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 lewatmercant_id. Kalau satumercant_idbisa 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
- Signature Verification — webhook, token payment link, notifikasi ERP semuanya ditandatangani/diverifikasi.
- Idempotency — cegah duplikasi order/notifikasi ERP; state disimpan konsisten pakai
order_idyang sudah disanitasi (Midtrans-safe) di semua map internal (activeOrders,notifiedOrders,orderRetryCount,orderMerchantId), sehingga tidak ada mismatch walauorder_idasli mengandung karakter seperti:. - API Key Authentication —
X-API-KEYuntuk/createtransaksidanPOST /api/payment-links. Dev mode fallback (bypass) hanya aktif kalauNODE_ENV !== 'production'dan key belum di-set. - HTTP Basic Auth untuk admin tooling —
/api/logs*,/openapi.json,/docsdilindungiLOG_BASIC_AUTH_USER/PASS, dan tertutup total secara default lewatLOG_EXPOSE_API=false(harus di-set eksplisittrueuntuk mengaktifkan). Wajib setLOG_BASIC_AUTH_USER/PASSdi production sebelum mengaktifkanLOG_EXPOSE_API, karena gerbang Basic Auth-nya sendiri bergantung padaNODE_ENV=productionuntuk benar-benar dipaksa (lihat poin 8). - Rate Limiting —
/createtransaksi,/api/payment-links,/api/payments/charge,/api/payments/snap/tokendibatasi per-IP (RATE_LIMIT_MAXrequest perRATE_LIMIT_WINDOW_MS) untuk mencegah bot abuse / card-testing pada endpoint yang memang harus tetap bisa diakses langsung dari browser tanpa API key. - CORS — dibatasi lewat
CORS_ALLOWED_ORIGINS(comma-separated). Kosong = izinkan semua origin (default dev). - Payload Masking — field sensitif (card number, CVV, token, server key) otomatis disamarkan di log.
NODE_ENVproduction — banyak gerbang keamanan (API key, Basic Auth) bergantung padaisDevEnv(). Pastikan proses production (ecosystem.config.cjs) benar-benar setNODE_ENV=production, lalupm2 restart <app> --update-envsetelah 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.