docs: rewrite README with backend setup, security, and tooling references

Root README.md was frontend-only and didn't mention the backend at all.
server/README.md was stale relative to the endpoint/security changes in this
branch (missing POST /api/payment-links, the sanitized order_id behavior,
rate limiting, CORS, log retention, state persistence, admin auth) and linked
to a temp/README.md that no longer exists. Both now point to /docs, /openapi.json,
and the Postman collection as the source of truth for the API surface.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tengku Achmad 2026-08-03 22:49:48 +07:00
parent 14c305b6bf
commit 77bc718c09
2 changed files with 176 additions and 341 deletions

View File

@ -1,45 +1,72 @@
# Core Midtrans CIFO (Frontend) # Midtrans Middleware (CIFO)
Frontend Vite + React untuk integrasi Midtrans Core API dengan UI kustom. Vite + React frontend dan Express backend untuk integrasi pembayaran Midtrans (Core API + Snap) dengan sistem ERP.
## Struktur Project
```
server/ Express backend (single-file: server/index.cjs)
src/ Frontend React (checkout, halaman /pay, dashboard demo)
postman/ Postman collection — seluruh endpoint API
tests/ Script testing manual untuk backend
scripts/ Utility script (mis. fetch-logos.mjs)
```
## Setup ## Setup
1) Duplikasi file contoh env dan isi nilainya: 1) Duplikasi file contoh env dan isi nilainya:
```bash ```bash
cp .env.example .env.local cp .env.example .env
``` ```
Isi `.env.local` (lihat `.env.example` untuk referensi): Isi `.env` sesuai kebutuhan — lihat `.env.example` untuk daftar lengkap variabel beserta penjelasannya (Midtrans key, `EXTERNAL_API_KEY`, `PAYMENT_LINK_SECRET`, `LOG_BASIC_AUTH_USER/PASS`, dll). Generate secret yang kuat dengan:
``` ```bash
VITE_API_BASE_URL=http://localhost:8000/api node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
VITE_MIDTRANS_CLIENT_KEY=YOUR_CLIENT_KEY
VITE_MIDTRANS_ENV=sandbox
``` ```
2) Jalankan pengembangan: 2) Install dependencies:
```bash ```bash
npm install npm install
npm run dev
``` ```
App akan tersedia di `http://localhost:5173/` (atau port lain jika 5173 dipakai). 3) Jalankan backend dan frontend (dua terminal terpisah):
```bash
npm run server # backend Express, default http://localhost:8000
npm run dev # frontend Vite, default http://localhost:5173
```
## API Backend
Dokumentasi lengkap seluruh endpoint ada di dua tempat:
- **Interaktif (Scalar)** — buka `http://localhost:8000/docs` di browser (dilindungi HTTP Basic Auth, kredensial sama dengan `/api/logs*`, lihat `LOG_BASIC_AUTH_USER`/`LOG_BASIC_AUTH_PASS`). Spec mentah tersedia di `/openapi.json`.
- **Postman** — import `postman/Midtrans-Middleware.postman_collection.json`, sudah berisi seluruh route (payment link, charge, snap token, webhook, logs, dev/test endpoints) dikelompokkan per folder.
Detail arsitektur & alur pembayaran ada di [`server/README.md`](server/README.md).
## Alat Bantu Operasional
- **Log viewer**`http://localhost:8000/api/logs/view` (dilindungi Basic Auth). Bisa filter per level, cari teks/order_id, dan klik nilai apa pun untuk trace lintas baris log yang berkaitan. Log otomatis dihapus setelah `LOG_RETENTION_DAYS` (default 30 hari).
- **Payment link** dibuat lewat `POST /api/payment-links` (payload langsung `order_id`/`nominal`/`customer`/`expire_at`) atau `POST /createtransaksi` (payload ERP `mercant_id`/`item[]`).
## Catatan Integrasi Midtrans ## Catatan Integrasi Midtrans
- Client Key hanya digunakan di frontend (mis. tokenisasi kartu/3DS). Server Key TIDAK pernah di frontend. - Client Key hanya digunakan di frontend (mis. tokenisasi kartu/3DS, Snap.js). Server Key **tidak pernah** dikirim ke frontend.
- Semua request ke Midtrans dilakukan lewat backend (`VITE_API_BASE_URL`). Frontend memanggil endpoint seperti `/payments/:orderId/status`. - Order pembayaran punya dua alur: (1) link yang dibagikan ke pelanggan (dibuat lewat `/createtransaksi` atau `/api/payment-links`, di-resolve oleh halaman `/pay/:token`), dan (2) checkout langsung dari browser (`/api/payments/charge`, `/api/payments/snap/token`) tanpa pra-registrasi — keduanya diberi rate limit untuk mencegah abuse.
- Status real-time dapat diimplementasikan via polling (TanStack Query) atau SSE/WebSocket dari backend. - `order_id` yang mengandung karakter selain alfanumerik/`- _ ~ .` (mis. `:`) otomatis disanitasi jadi `.` sebelum dikirim ke Midtrans.
- Status real-time dapat diimplementasikan via polling (TanStack Query) atau webhook (`POST /api/payments/notification`) yang meneruskan notifikasi ke ERP.
## Struktur Env di Kode ## Struktur Env di Kode Frontend
- Akses env melalui modul `src/lib/env.ts`: Akses env melalui modul `src/lib/env.ts`:
- `Env.API_BASE_URL` - `Env.API_BASE_URL`
- `Env.MIDTRANS_CLIENT_KEY` - `Env.MIDTRANS_CLIENT_KEY`
- `Env.MIDTRANS_ENV` - `Env.MIDTRANS_ENV`
## Lisensi ## Lisensi
Internal project skeleton. Internal project — CIFO Group.

View File

@ -2,7 +2,9 @@
Backend Express.js server untuk integrasi pembayaran Midtrans dengan sistem ERP. Backend Express.js server untuk integrasi pembayaran Midtrans dengan sistem ERP.
## 📋 Daftar Isi > 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) - [Fitur Utama](#fitur-utama)
- [Konfigurasi Environment](#konfigurasi-environment) - [Konfigurasi Environment](#konfigurasi-environment)
@ -10,17 +12,18 @@ Backend Express.js server untuk integrasi pembayaran Midtrans dengan sistem ERP.
- [Payment Flow](#payment-flow) - [Payment Flow](#payment-flow)
- [Testing](#testing) - [Testing](#testing)
- [Logging](#logging) - [Logging](#logging)
- [Keamanan](#keamanan)
## 🚀 Fitur Utama ## Fitur Utama
### 1. **Dual Mode Payment** ### 1. **Dual Mode Payment**
- **CORE API**: Bank Transfer, Credit Card, GoPay/QRIS, Convenience Store - **CORE API**: Bank Transfer, Credit Card, GoPay/QRIS, Convenience Store
- **SNAP**: Hosted payment interface dengan UI Midtrans - **SNAP**: Hosted payment interface dengan UI Midtrans
### 2. **Payment Link Generation** ### 2. **Payment Link Generation**
- Generate secure payment link dengan signature validation - Dua bentuk payload: langsung (`order_id`/`nominal`/`customer`/`expire_at`) atau ERP (`mercant_id`/`item[]`)
- Configurable TTL (Time To Live) - Signature HMAC-SHA256 pada token, TTL configurable
- Token-based authentication - `order_id` yang mengandung karakter selain `A-Za-z0-9-_~.` (mis. `:`) otomatis disanitasi jadi `.` sebelum dikirim ke Midtrans
### 3. **ERP Integration** ### 3. **ERP Integration**
- Notifikasi otomatis ke sistem ERP setelah pembayaran sukses - Notifikasi otomatis ke sistem ERP setelah pembayaran sukses
@ -32,207 +35,115 @@ Backend Express.js server untuk integrasi pembayaran Midtrans dengan sistem ERP.
- Signature verification - Signature verification
- Idempotent notification handling - Idempotent notification handling
### 5. **Advanced Logging** ### 5. **State Persisten**
- Level-based logging (debug, info, warn, error) - `activeOrders`, `notifiedOrders`, `orderRetryCount`, `orderMerchantId` disimpan ke `server/data/state.json` (auto-save), jadi tidak hilang saat server restart
- In-memory log buffer
- Payload masking untuk sensitive data
- Jakarta timezone (WIB/UTC+7)
## ⚙️ Konfigurasi Environment ### 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 Configuration
```env ```env
# Required
MIDTRANS_SERVER_KEY=your-server-key MIDTRANS_SERVER_KEY=your-server-key
MIDTRANS_CLIENT_KEY=your-client-key MIDTRANS_CLIENT_KEY=your-client-key
MIDTRANS_IS_PRODUCTION=false MIDTRANS_IS_PRODUCTION=false
# Payment Method Toggles
ENABLE_BANK_TRANSFER=true ENABLE_BANK_TRANSFER=true
ENABLE_CREDIT_CARD=true ENABLE_CREDIT_CARD=true
ENABLE_GOPAY=true ENABLE_GOPAY=true
ENABLE_CSTORE=true ENABLE_CSTORE=true
``` ```
### Payment Link Configuration ### Payment Link & External API
```env ```env
# External API Access EXTERNAL_API_KEY=your-api-key # X-API-KEY untuk /createtransaksi & POST /api/payment-links
EXTERNAL_API_KEY=your-api-key PAYMENT_LINK_SECRET=your-signing-secret # HMAC secret untuk token link
# Payment Link Settings
PAYMENT_LINK_SECRET=your-secret-for-signing
PAYMENT_LINK_TTL_MINUTES=1440 PAYMENT_LINK_TTL_MINUTES=1440
PAYMENT_LINK_BASE=http://localhost:5174/pay PAYMENT_LINK_BASE=http://localhost:5174/pay
``` ```
### ERP Integration ### ERP Integration
```env ```env
# Single URL (legacy)
ERP_NOTIFICATION_URL=https://your-erp.com/api/payment-notification ERP_NOTIFICATION_URL=https://your-erp.com/api/payment-notification
ERP_CLIENT_SECRET=your-erp-client-secret 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)
# Multi-URL (recommended)
ERP_NOTIFICATION_URLS=https://erp1.com/api/notif,https://erp2.com/api/notif
# Toggle
ERP_ENABLE_NOTIF=true ERP_ENABLE_NOTIF=true
``` ```
### Logging Configuration ### Logging & Admin Access
```env ```env
# Logging Level: debug, info, warn, error
LOG_LEVEL=info LOG_LEVEL=info
LOG_EXPOSE_API=false # WARNING: default true kalau tidak di-set
# Expose /api/logs endpoint (dev only) LOG_BASIC_AUTH_USER= # wajib diisi di production untuk lindungi /api/logs* dan /docs
LOG_EXPOSE_API=true LOG_BASIC_AUTH_PASS=
LOG_RETENTION_DAYS=30
# In-memory buffer size
LOG_BUFFER_SIZE=1000 LOG_BUFFER_SIZE=1000
``` ```
### Server Configuration ### CORS & Rate Limiting
```env ```env
PORT=8000 CORS_ALLOWED_ORIGINS=https://your-frontend.example.com # kosong = izinkan semua origin (dev default)
NODE_ENV=development RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX=20
``` ```
## 📡 API Endpoints ### Server
```env
PORT=8000
NODE_ENV=production # HARUS "production" di server production — banyak gerbang keamanan bergantung pada ini
```
## API Endpoints
### Health & Config ### Health & Config
#### `GET /api/health` #### `GET /api/health`
Health check endpoint. Health check endpoint.
**Response:**
```json
{
"ok": true,
"env": {
"isProduction": false,
"hasServerKey": true,
"hasClientKey": true
}
}
```
#### `GET /api/config` #### `GET /api/config`
Get current payment configuration. Ambil payment toggles & Midtrans client key saat ini.
**Response:** #### `POST /api/config` (non-production only)
```json Update payment toggles saat runtime. Return 403 kalau `NODE_ENV=production`.
{
"paymentToggles": {
"bank_transfer": true,
"credit_card": true,
"gopay": true,
"cstore": true
},
"midtransEnv": "sandbox",
"clientKey": "SB-Mid-client-xxx"
}
```
#### `POST /api/config` (Dev Only)
Update payment toggles at runtime.
**Request:**
```json
{
"paymentToggles": {
"bank_transfer": false
}
}
```
### Payment Operations ### Payment Operations
#### `POST /api/payments/charge` #### `POST /api/payments/charge`
Create payment transaction via Midtrans Core API. Buat transaksi Core API (bank transfer, card, GoPay/QRIS, cstore). Dipanggil langsung dari browser checkout, tanpa perlu pra-registrasi order. **Rate-limited.**
**Headers:**
```
Content-Type: application/json
```
**Request Body:**
```json
{
"payment_type": "bank_transfer",
"transaction_details": {
"order_id": "order-123",
"gross_amount": 150000
},
"bank_transfer": {
"bank": "bca"
}
}
```
**Response:**
```json
{
"status_code": "201",
"status_message": "Success",
"transaction_id": "xxx",
"order_id": "order-123",
"va_numbers": [
{
"bank": "bca",
"va_number": "12345678901"
}
]
}
```
#### `POST /api/payments/snap/token` #### `POST /api/payments/snap/token`
Generate Snap token for hosted payment. Generate Snap token untuk hosted payment popup. Dipanggil langsung dari browser (`PayPage`/`CheckoutPage`). **Rate-limited.**
**Request:**
```json
{
"transaction_details": {
"order_id": "order-123",
"gross_amount": 150000
},
"customer_details": {
"first_name": "John",
"email": "john@example.com"
}
}
```
**Response:**
```json
{
"token": "snap-token-xxx"
}
```
#### `GET /api/payments/:orderId/status` #### `GET /api/payments/:orderId/status`
Check payment status. Cek status transaksi (passthrough Midtrans). Juga memicu fallback notifikasi ERP kalau status sudah sukses tapi belum ter-notify.
**Response:**
```json
{
"status_code": "200",
"transaction_status": "settlement",
"order_id": "order-123",
"gross_amount": "150000.00"
}
```
### Payment Link ### Payment Link
#### `POST /createtransaksi` #### `POST /api/payment-links` — payload langsung
Generate payment link (external ERP endpoint). Requires `X-API-KEY`. **Rate-limited.**
```json
**Headers:** {
"order_id": "ERPSKRIP-2608030000000627:TKG-260803000063",
"nominal": 179000,
"customer": { "name": "Yusnika Nur Faidah", "phone": "0881022144656", "email": "yusnika_nur_faidah@example.com" },
"expire_at": 1785852063058
}
``` ```
X-API-KEY: your-external-api-key `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`.
Content-Type: application/json
**Response:**
```json
{ "status": "200", "messages": "SUCCESS", "data": { "url": "...", "order_id": "...", "midtrans_order_id": "...", "expire_at": 1785852063058 } }
``` ```
**Request:** #### `POST /createtransaksi` — payload ERP (mercant_id/item)
Requires `X-API-KEY`. **Rate-limited.**
```json ```json
{ {
"mercant_id": "merchant-001", "mercant_id": "merchant-001",
@ -240,134 +151,61 @@ Content-Type: application/json
"nama": "John Doe", "nama": "John Doe",
"email": "john@example.com", "email": "john@example.com",
"no_telepon": "081234567890", "no_telepon": "081234567890",
"item": [ "item": [{ "item_id": "product-123", "nama": "Product Name", "harga": 150000, "qty": 1 }],
{
"item_id": "product-123",
"nama": "Product Name",
"harga": 150000,
"qty": 1
}
],
"allowed_methods": ["bank_transfer", "gopay"] "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:** **Response:**
```json ```json
{ { "status": "200", "messages": "SUCCESS", "data": { "url": "http://localhost:5174/pay/eyJ2Ijox..." } }
"status": "200",
"messages": "SUCCESS",
"data": {
"url": "http://localhost:5174/pay/eyJ2Ijox..."
}
}
``` ```
#### `GET /api/payment-links/:token` #### `GET /api/payment-links/:token`
Resolve payment link token. Resolve token jadi detail order: `{ order_id, nominal, customer, expire_at, allowed_methods }`.
**Response:**
```json
{
"order_id": "merchant-001:product-123",
"nominal": 150000,
"customer": {
"name": "John Doe",
"email": "john@example.com",
"phone": "081234567890"
},
"expire_at": 1733280000000,
"allowed_methods": ["bank_transfer", "gopay"]
}
```
### Webhook ### Webhook
#### `POST /api/payments/notification` #### `POST /api/payments/notification`
Midtrans webhook handler (unified for CORE & SNAP). 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.
**Request (from Midtrans):** ### Logs & Docs (internal — HTTP Basic Auth di production)
```json
{
"order_id": "order-123",
"transaction_status": "settlement",
"gross_amount": "150000.00",
"signature_key": "xxx"
}
```
**Response:** | Endpoint | Keterangan |
```json |---|---|
{ | `GET /api/logs` | Log in-memory terbaru (`?limit`, `?level`, `?q`) |
"ok": true | `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) |
### Logging (Dev Only) ### Dev/Test (aktif hanya kalau `LOG_EXPOSE_API=true`)
#### `GET /api/logs?limit=100&level=info&q=payment` - `POST /api/echo`, `POST /api/echo2` — mock endpoint untuk testing callback ERP
Get recent logs. - `POST /api/test/notify-erp` — trigger `notifyERP()` langsung, bypass webhook
**Query Parameters:** ## Payment Flow
- `limit`: Max entries (1-1000, default: 100)
- `level`: Filter by level (debug|info|warn|error)
- `q`: Search keyword
**Response:**
```json
{
"count": 50,
"items": [
{
"ts": "2024-12-04T12:00:00.000+07:00",
"level": "info",
"msg": "charge.request",
"meta": {
"id": "abc123",
"payment_type": "bank_transfer"
}
}
]
}
```
### Testing (Dev Only)
#### `POST /api/echo`
Echo endpoint untuk testing ERP notification.
#### `POST /api/test/notify-erp`
Manual trigger ERP notification.
**Request:**
```json
{
"orderId": "order-123",
"nominal": "150000",
"mercant_id": "merchant-001"
}
```
## 🔄 Payment Flow
### 1. Payment Link Creation Flow ### 1. Payment Link Creation Flow
``` ```
ERP System → POST /createtransaksi → Server generates token → Payment URL ERP System → POST /createtransaksi atau POST /api/payment-links → Server generate token → Payment URL
``` ```
### 2. Payment Execution Flow ### 2. Payment Execution Flow
``` ```
Customer → Payment URL → Frontend resolves token → Customer → Payment URL → Frontend resolve token →
Choose method → POST /api/payments/charge or SNAP → Midtrans Pilih metode → POST /api/payments/charge atau /api/payments/snap/token → Midtrans
``` ```
### 3. Payment Completion Flow ### 3. Payment Completion Flow
``` ```
Midtrans → Webhook → Verify signature → Update ledger → Midtrans → Webhook → Verifikasi signature → Update ledger →
Notify ERP → Mark order complete Notify ERP → Tandai order selesai
``` ```
### 4. ERP Notification Format ### 4. Format Notifikasi ke ERP
```json ```json
{ {
"mercant_id": "merchant-001", "mercant_id": "merchant-001",
@ -376,115 +214,85 @@ Notify ERP → Mark order complete
"signature": "sha512-hash" "signature": "sha512-hash"
} }
``` ```
**Perhitungan Signature:** `SHA512(mercant_id + status_code + nominal + ERP_CLIENT_ID)`
**Signature Calculation:** > 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.
```
SHA512(mercant_id + status_code + nominal + ERP_CLIENT_SECRET)
```
## 🧪 Testing ## Testing
Lihat folder `tests/` untuk file-file testing: Lihat folder `tests/` untuk file-file testing:
```bash ```bash
# Test create payment link
node tests/test-create-payment-link.cjs node tests/test-create-payment-link.cjs
# Test frontend payload
node tests/test-frontend-payload.cjs node tests/test-frontend-payload.cjs
# Test snap token
node tests/test-snap-token.cjs node tests/test-snap-token.cjs
``` ```
Lihat `tests/README.md` untuk detail lengkap. 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 ## Logging
### Log Levels ### Log Levels
- **debug**: Detailed information, typically of interest only when diagnosing problems - **debug**: Detail untuk diagnosa masalah
- **info**: General informational messages - **info**: Informasi umum
- **warn**: Warning messages for potentially harmful situations - **warn**: Peringatan
- **error**: Error events that might still allow the application to continue running - **error**: Error yang masih memungkinkan aplikasi jalan
### Log Format ### Format Log (file, satu baris per event)
``` ```
[2024-12-04T12:00:00.000+07:00] [info] charge.request {"id": "abc123", "payment_type": "bank_transfer"} [2026-08-03T21:35:54.746+07:00] [INFO ] webhook.notifying_erp | {"order_id":"ERPSKRIP-2608030000000637.TKG-260801001361","mercant_id":"ERPSKRIP-2608030000000637"}
``` ```
### Important Log Events Paling nyaman dibaca lewat `GET /api/logs/view` (filter level, cari teks, klik nilai untuk trace) daripada membaca file mentah.
#### Payment Lifecycle ### Event Penting
- `charge.request`: Payment charge initiated
- `charge.success`: Charge successful
- `charge.error`: Charge failed
- `status.request`: Status check requested
- `webhook.received`: Webhook notification received
#### ERP Integration **Payment Lifecycle:** `charge.request`, `charge.success`, `charge.error`, `status.request`, `webhook.received`
- `erp.notify.start`: ERP notification started
- `erp.notify.success`: ERP notified successfully
- `erp.notify.error`: ERP notification failed
- `erp.notify.skip`: Notification skipped (already sent or disabled)
#### Security **Payment Link:** `payment-links.create.issued`, `createtransaksi.issued`, `paymentlink.resolve.success`
- `webhook.signature.invalid`: Invalid webhook signature
- `createtransaksi.unauthorized`: Unauthorized API key
## 🔒 Security Features **ERP Integration:** `erp.notify.start`, `erp.notify.success`, `erp.notify.error`, `erp.notify.skip`
1. **Signature Verification** **Security:** `webhook.signature.invalid`, `createtransaksi.unauthorized`, `payment-links.create.unauthorized`, `admin.auth.failed`, `rate_limit.blocked`
- Webhook signature validation
- Payment link token signing
- ERP notification signing
2. **Idempotency** ## Keamanan
- Prevent duplicate order creation
- Prevent duplicate ERP notifications
- Block re-charge for pending orders
3. **API Key Authentication** 1. **Signature Verification** — webhook, token payment link, notifikasi ERP semuanya ditandatangani/diverifikasi.
- External API key for `/createtransaksi` 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 `:`.
- Dev mode fallback for easier local testing 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 <app> --update-env` setelah mengubahnya.
4. **Payload Masking** ## Running the Server
- Sensitive fields masked in logs
- Card numbers, CVV, tokens automatically hidden
## 🚀 Running the Server
```bash ```bash
# Install dependencies
npm install npm install
npm run server # atau: node server/index.cjs
# Start server # Server jalan di http://localhost:8000
node server/index.cjs
# Server runs on http://localhost:8000
``` ```
## 📚 Related Documentation ## Common Issues
- [Tests README](../tests/README.md) - Testing documentation ### "Transaction already pending"
- [Temp Files README](../temp/README.md) - Temporary files info **Penyebab**: Order ID sudah punya transaksi pending di Midtrans.
- [Frontend README](../README.md) - Main project README **Solusi**: Pakai instruksi pembayaran yang ada, atau buat order baru dengan ID berbeda.
## 🐛 Common Issues ### "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.
### Issue: "Transaction already pending" ### "Invalid signature on webhook"
**Cause**: Order ID already has pending transaction in Midtrans **Penyebab**: `MIDTRANS_SERVER_KEY` salah, atau webhook dari sumber tak sah.
**Solution**: Use existing payment instructions or create new order with different ID **Solusi**: Pastikan `MIDTRANS_SERVER_KEY` sama dengan akun Midtrans yang memproses transaksi tersebut (sandbox vs production beda key).
### Issue: "ERP notification failed" ### Log/`/docs` mengembalikan 401 atau 503
**Cause**: ERP endpoint unreachable or signature mismatch **Penyebab**: `LOG_BASIC_AUTH_USER`/`PASS` belum di-set di production (`NODE_ENV=production`).
**Solution**: Check `ERP_NOTIFICATION_URLS` and `ERP_CLIENT_SECRET` configuration **Solusi**: Set kedua env var tersebut, restart server.
### Issue: "Invalid signature on webhook" ## Support
**Cause**: Incorrect server key or webhook from unauthorized source
**Solution**: Verify `MIDTRANS_SERVER_KEY` matches your Midtrans account
## 📞 Support
Untuk bantuan lebih lanjut, hubungi tim development atau lihat dokumentasi Midtrans:
- [Midtrans API Documentation](https://docs.midtrans.com/) - [Midtrans API Documentation](https://docs.midtrans.com/)
- [Midtrans Node.js Library](https://github.com/Midtrans/midtrans-nodejs-client) - [Midtrans Node.js Library](https://github.com/Midtrans/midtrans-nodejs-client)