# 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](#fitur-utama) - [Konfigurasi Environment](#konfigurasi-environment) - [API Endpoints](#api-endpoints) - [Payment Flow](#payment-flow) - [Testing](#testing) - [Logging](#logging) - [Keamanan](#keamanan) ## 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_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 ```env 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 ```env 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 ```env 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 ```env LOG_LEVEL=info LOG_EXPOSE_API=false # WARNING: default true 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 ```env CORS_ALLOWED_ORIGINS=https://your-frontend.example.com # kosong = izinkan semua origin (dev default) RATE_LIMIT_WINDOW_MS=60000 RATE_LIMIT_MAX=20 ``` ### Server ```env 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.** ```json { "order_id": "ERPSKRIP-2608030000000627:TKG-260803000063", "nominal": 179000, "customer": { "name": "Yusnika Nur Faidah", "phone": "0881022144656", "email": "yusnika_nur_faidah@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:** ```json { "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.** ```json { "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:** ```json { "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 ### 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 ```json { "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: ```bash 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":"ERPSKRIP-2608030000000637.TKG-260801001361","mercant_id":"ERPSKRIP-2608030000000637"} ``` 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 Authentication** — `X-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` dilindungi `LOG_BASIC_AUTH_USER`/`PASS`. **Wajib di-set di production** — endpoint ini terbuka penuh secara default (`LOG_EXPOSE_API` default `true`) kalau tidak dikonfigurasi. 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 --update-env` setelah mengubahnya. ## Running the Server ```bash 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 - [Midtrans API Documentation](https://docs.midtrans.com/) - [Midtrans Node.js Library](https://github.com/Midtrans/midtrans-nodejs-client)