From 6472e953101ad8c8f44acb0edb733cf2923c2c8f Mon Sep 17 00:00:00 2001 From: CIFO Dev Date: Wed, 12 Nov 2025 10:29:37 +0700 Subject: [PATCH] fixing merchant id --- server/index.cjs | 66 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/server/index.cjs b/server/index.cjs index da71cc1..e9494f9 100644 --- a/server/index.cjs +++ b/server/index.cjs @@ -38,7 +38,6 @@ function parseEnable(v) { const ERP_NOTIFICATION_URL = process.env.ERP_NOTIFICATION_URL || '' const ERP_ENABLE_NOTIF = parseEnable(process.env.ERP_ENABLE_NOTIF) const ERP_CLIENT_ID = process.env.ERP_CLIENT_ID || '' -const ERP_MERCANT_ID = process.env.ERP_MERCANT_ID || process.env.ERP_MERCHANT_ID || '' const notifiedOrders = new Set() // --- Logger utilities @@ -104,6 +103,8 @@ const PAYMENT_LINK_SECRET = process.env.PAYMENT_LINK_SECRET || '' const PAYMENT_LINK_TTL_MINUTES = parseInt(process.env.PAYMENT_LINK_TTL_MINUTES || '30', 10) const PAYMENT_LINK_BASE = process.env.PAYMENT_LINK_BASE || 'http://localhost:5174/pay' const activeOrders = new Map() // order_id -> expire_at +// Map untuk menyimpan mercant_id per order_id agar notifikasi ERP bisa dinamis +const orderMerchantId = new Map() // order_id -> mercant_id function isDevEnv() { return (process.env.NODE_ENV || '').toLowerCase() !== 'production' } function verifyExternalKey(req) { @@ -266,7 +267,27 @@ app.get('/api/payments/:orderId/status', async (req, res) => { logInfo('status.request', { id: req.id, orderId }) const status = await core.transaction.status(orderId) logInfo('status.success', { id: req.id, orderId, transaction_status: status?.transaction_status }) + // Respond immediately with status res.json(status) + + // Fallback: selain webhook, jika status di sini sudah sukses (settlement/capture+accept), kirim notifikasi ke ERP + setImmediate(async () => { + try { + if (isSuccessfulMidtransStatus(status)) { + const nominal = String(status?.gross_amount || '') + if (!notifiedOrders.has(orderId)) { + notifiedOrders.add(orderId) + activeOrders.delete(orderId) + logInfo('status.notify.erp.trigger', { orderId, transaction_status: status?.transaction_status }) + await notifyERP({ orderId, nominal }) + } else { + logInfo('erp.notify.skip', { orderId, reason: 'already_notified' }) + } + } + } catch (e) { + logError('status.notify.error', { orderId, message: e?.message }) + } + }) } catch (e) { const msg = e?.message || 'Status check failed' logError('status.error', { id: req.id, orderId, message: msg }) @@ -298,6 +319,10 @@ app.post('/createtransaksi', async (req, res) => { (primaryItemId && mercantId) ? `${mercantId}:${primaryItemId}` : (primaryItemId || mercantId || req?.body?.order_id || req?.body?.item_id || '') ) + // Simpan mercant_id per order agar dapat digunakan saat notifikasi ERP + if (mercantId) { + try { orderMerchantId.set(order_id, mercantId) } catch {} + } // Bentuk customer dari field nama/no_telepon/email const customer = { @@ -423,24 +448,43 @@ function computeErpSignature(mercantId, statusCode, nominal, clientId) { } } -async function notifyERP({ orderId, nominal }) { +// Resolve mercant_id untuk sebuah order_id: +// 1) gunakan map yang tersimpan dari createtransaksi +// 2) jika order_id memakai skema "mercant_id:item_id", ambil prefix sebelum ':' +// 3) fallback ke ERP_MERCANT_ID dari env (untuk kasus lama) +function resolveMercantId(orderId) { + try { + if (orderMerchantId.has(orderId)) return orderMerchantId.get(orderId) + if (typeof orderId === 'string' && orderId.includes(':')) { + const [m] = orderId.split(':') + if (m) return m + } + } catch {} + return '' +} + +async function notifyERP({ orderId, nominal, mercantId }) { if (!ERP_ENABLE_NOTIF) { logInfo('erp.notify.skip', { reason: 'disabled' }) return } - if (!ERP_NOTIFICATION_URL || !ERP_CLIENT_ID || !ERP_MERCANT_ID) { - logWarn('erp.notify.missing_config', { hasUrl: !!ERP_NOTIFICATION_URL, hasClientId: !!ERP_CLIENT_ID, hasMercantId: !!ERP_MERCANT_ID }) + // Untuk notifikasi dinamis, hanya URL dan client secret yang wajib + if (!ERP_NOTIFICATION_URL || !ERP_CLIENT_ID) { + logWarn('erp.notify.missing_config', { hasUrl: !!ERP_NOTIFICATION_URL, hasClientId: !!ERP_CLIENT_ID }) return } const statusCode = '200' - const signature = computeErpSignature(ERP_MERCANT_ID, statusCode, nominal, ERP_CLIENT_ID) + const mId = mercantId || resolveMercantId(orderId) + if (!mId) { + logWarn('erp.notify.skip', { orderId, reason: 'missing_mercant_id' }) + return + } + const signature = computeErpSignature(mId, statusCode, nominal, ERP_CLIENT_ID) + // Payload ERP harus flat: { mercant_id, nominal, status_code, signature } const payload = { - data: { - mercant_id: ERP_MERCANT_ID, - status_code: statusCode, - nominal: nominal, - client_id: ERP_CLIENT_ID, - }, + mercant_id: mId, + status_code: statusCode, + nominal: nominal, signature, } logInfo('erp.notify.start', { orderId, url: ERP_NOTIFICATION_URL })