Merge pull request 'feat(server): support multiple ERP notification URLs (ERP_NOTIFICATION_URLS)\n\n- Add env ERP_NOTIFICATION_URLS (comma-separated) with fallback to ERP_NOTIFICATION_URL\n- Update' (#11) from feat/payment-link-flow into main
Reviewed-on: #11
This commit is contained in:
commit
2494f3cedd
|
|
@ -41,6 +41,21 @@ const ERP_ENABLE_NOTIF = parseEnable(process.env.ERP_ENABLE_NOTIF)
|
|||
const ERP_CLIENT_SECRET = process.env.ERP_CLIENT_SECRET || process.env.ERP_CLIENT_ID || ''
|
||||
const notifiedOrders = new Set()
|
||||
|
||||
// Mendukung banyak endpoint ERP (comma-separated) via env ERP_NOTIFICATION_URLS
|
||||
function parseList(value) {
|
||||
if (!value) return []
|
||||
return String(value)
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
const ERP_NOTIFICATION_URLS = (() => {
|
||||
const multi = parseList(process.env.ERP_NOTIFICATION_URLS)
|
||||
if (multi.length > 0) return multi
|
||||
return ERP_NOTIFICATION_URL ? [ERP_NOTIFICATION_URL] : []
|
||||
})()
|
||||
|
||||
// --- Logger utilities
|
||||
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase()
|
||||
const levelOrder = { debug: 0, info: 1, warn: 2, error: 3 }
|
||||
|
|
@ -534,8 +549,8 @@ async function notifyERP({ orderId, nominal, mercantId }) {
|
|||
return false
|
||||
}
|
||||
// Untuk notifikasi dinamis, hanya URL dan client secret yang wajib
|
||||
if (!ERP_NOTIFICATION_URL || !ERP_CLIENT_SECRET) {
|
||||
logWarn('erp.notify.missing_config', { hasUrl: !!ERP_NOTIFICATION_URL, hasClientSecret: !!ERP_CLIENT_SECRET })
|
||||
if (ERP_NOTIFICATION_URLS.length === 0 || !ERP_CLIENT_SECRET) {
|
||||
logWarn('erp.notify.missing_config', { urlsCount: ERP_NOTIFICATION_URLS.length, hasClientSecret: !!ERP_CLIENT_SECRET })
|
||||
return false
|
||||
}
|
||||
const statusCode = '200'
|
||||
|
|
@ -560,20 +575,29 @@ async function notifyERP({ orderId, nominal, mercantId }) {
|
|||
signature_present: typeof signature !== 'undefined',
|
||||
signature_length: (typeof signature === 'string') ? signature.length : -1,
|
||||
})
|
||||
logInfo('erp.notify.start', { orderId, url: ERP_NOTIFICATION_URL })
|
||||
logInfo('erp.notify.start', { orderId, urls: ERP_NOTIFICATION_URLS })
|
||||
const results = await Promise.allSettled(
|
||||
ERP_NOTIFICATION_URLS.map(async (url) => {
|
||||
try {
|
||||
const res = await postJson(ERP_NOTIFICATION_URL, payload)
|
||||
logInfo('erp.notify.success', { orderId, status: res.status })
|
||||
const res = await postJson(url, payload)
|
||||
logInfo('erp.notify.success', { orderId, url, status: res.status })
|
||||
return true
|
||||
} catch (e) {
|
||||
logError('erp.notify.error', {
|
||||
orderId,
|
||||
url,
|
||||
status: e?.statusCode,
|
||||
message: e?.message,
|
||||
body: typeof e?.body === 'string' ? e.body.slice(0, 256) : undefined,
|
||||
})
|
||||
return false
|
||||
}
|
||||
})
|
||||
)
|
||||
const okCount = results.reduce((acc, r) => acc + (r.status === 'fulfilled' && r.value ? 1 : 0), 0)
|
||||
const failCount = ERP_NOTIFICATION_URLS.length - okCount
|
||||
logInfo('erp.notify.summary', { orderId, okCount, failCount, total: ERP_NOTIFICATION_URLS.length })
|
||||
return okCount > 0
|
||||
}
|
||||
|
||||
// Webhook endpoint for Midtrans notifications
|
||||
|
|
@ -621,6 +645,59 @@ app.post('/api/payments/webhook', async (req, res) => {
|
|||
}
|
||||
})
|
||||
|
||||
// Dev-only helpers: echo endpoint and manual ERP notify trigger
|
||||
if (LOG_EXPOSE_API) {
|
||||
// Echo incoming JSON for local testing (can be used as ERP_NOTIFICATION_URL)
|
||||
app.post('/api/echo', async (req, res) => {
|
||||
try {
|
||||
const body = req.body || {}
|
||||
const sig = body?.signature
|
||||
logDebug('erp.mock.receive', {
|
||||
has_signature: typeof sig !== 'undefined',
|
||||
signature_length: (typeof sig === 'string') ? sig.length : -1,
|
||||
body_length: JSON.stringify(body).length,
|
||||
})
|
||||
return res.json({ ok: true, received: body })
|
||||
} catch (e) {
|
||||
logError('erp.mock.error', { message: e?.message })
|
||||
return res.status(500).json({ error: 'ECHO_ERROR', message: e?.message || 'Echo failed' })
|
||||
}
|
||||
})
|
||||
|
||||
// Echo kedua untuk pengujian multi-URL lokal
|
||||
app.post('/api/echo2', async (req, res) => {
|
||||
try {
|
||||
const body = req.body || {}
|
||||
const sig = body?.signature
|
||||
logDebug('erp.mock.receive', {
|
||||
endpoint: 'echo2',
|
||||
has_signature: typeof sig !== 'undefined',
|
||||
signature_length: (typeof sig === 'string') ? sig.length : -1,
|
||||
body_length: JSON.stringify(body).length,
|
||||
})
|
||||
return res.json({ ok: true, received: body })
|
||||
} catch (e) {
|
||||
logError('erp.mock.error', { endpoint: 'echo2', message: e?.message })
|
||||
return res.status(500).json({ error: 'ECHO2_ERROR', message: e?.message || 'Echo2 failed' })
|
||||
}
|
||||
})
|
||||
|
||||
// Manually trigger ERP notification for testing signature presence in body
|
||||
app.post('/api/test/notify-erp', async (req, res) => {
|
||||
try {
|
||||
const { orderId, nominal, mercant_id } = req.body || {}
|
||||
if (!orderId && !mercant_id) {
|
||||
return res.status(400).json({ error: 'BAD_REQUEST', message: 'Provide orderId or mercant_id and nominal' })
|
||||
}
|
||||
const ok = await notifyERP({ orderId, nominal: String(nominal || ''), mercantId: mercant_id })
|
||||
return res.json({ ok })
|
||||
} catch (e) {
|
||||
logError('test.notify.error', { message: e?.message })
|
||||
return res.status(500).json({ error: 'TEST_NOTIFY_ERROR', message: e?.message || 'Notify test failed' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 8000
|
||||
app.listen(port, () => {
|
||||
console.log(`[server] listening on http://localhost:${port}/ (production=${isProduction})`)
|
||||
|
|
|
|||
Loading…
Reference in New Issue