import { createHmac } from "node:crypto"; const PORT = process.env.PORT || "3100"; const BASE = process.env.BASE_URL || `http://127.0.0.1:${PORT}`; const ADMIN_TOKEN = process.env.ADMIN_TOKEN || "admin-dev-token"; const MERCHANT_TOKEN = process.env.MERCHANT_TOKEN || "merchant-dev-token"; const MERCHANT_PORTAL_PASSWORD = process.env.MERCHANT_PORTAL_PASSWORD || "merchant"; const DEVICE_TOKEN = process.env.DEVICE_TOKEN || "device-dev-token"; const SECRET = process.env.INTEGRATION_WEBHOOK_SECRET || "dev-callback-secret"; function short(data) { const json = typeof data === 'string' ? data : JSON.stringify(data || {}); return json.length > 180 ? `${json.slice(0, 180)}...` : json; } async function req(path, options = {}) { const response = await fetch(`${BASE}${path}`, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, body: Object.prototype.hasOwnProperty.call(options, 'body') ? JSON.stringify(options.body) : undefined }); const text = await response.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = text; } if (!response.ok) { throw new Error(`${options._label || path} failed: ${response.status}`); } console.log(`${options._label || `${options.method || 'GET'} ${path}`} => ${response.status} ${short(body)}`); return body; } async function reqExpect(path, expectedStatus, options = {}) { const response = await fetch(`${BASE}${path}`, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, body: Object.prototype.hasOwnProperty.call(options, 'body') ? JSON.stringify(options.body) : undefined }); const text = await response.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = text; } if (response.status !== expectedStatus) { throw new Error(`${options._label || path} expected ${expectedStatus}, got ${response.status}`); } console.log(`${options._label || `${options.method || 'GET'} ${path}`} => ${response.status} ${short(body)}`); return body; } async function reqAdmin(path, opts = {}) { return req(path, { ...opts, headers: { ...(opts.headers || {}), Authorization: `Bearer ${ADMIN_TOKEN}` } }); } async function reqMerchant(path, merchantId, opts = {}) { return req(path, { ...opts, headers: { ...(opts.headers || {}), Authorization: `Bearer ${MERCHANT_TOKEN}`, 'X-Merchant-Id': merchantId } }); } async function reqDevice(path, opts = {}) { return req(path, { ...opts, headers: { ...(opts.headers || {}), Authorization: `Bearer ${DEVICE_TOKEN}` } }); } async function reqDeviceCredential(path, deviceId, secret, opts = {}) { return req(path, { ...opts, headers: { ...(opts.headers || {}), 'X-Device-Id': deviceId, 'X-Device-Secret': secret } }); } (async () => { await req('/health', { _label: 'GET /health' }); await req('/admin/login', { method: 'POST', body: { username: 'admin', password: 'admin' }, _label: 'POST /admin/login' }); await reqAdmin('/admin/seed/status', { _label: 'GET /admin/seed/status' }); const ts = Date.now(); const merchant = await reqAdmin('/admin/merchants', { method: 'POST', body: { legal_name: `Smoke Merchant ${ts}`, brand_name: `SMK-${ts}`, settlement_account_reference: `bank:${ts}`, settlement_account_type: 'merchant_bank_account', payout_mode: 'merchant_direct' }, _label: 'POST /admin/merchants' }); const merchantId = merchant?.data?.id; const outlet = await reqAdmin(`/admin/merchants/${merchantId}/outlets`, { method: 'POST', body: { name: `Outlet ${ts}` }, _label: 'POST /admin/merchants/:id/outlets' }); const outletId = outlet?.data?.id; const terminal = await reqAdmin(`/admin/outlets/${outletId}/terminals`, { method: 'POST', body: { terminal_code: `TERM-${ts}`, qr_mode: 'static' }, _label: 'POST /admin/outlets/:id/terminals' }); const terminalId = terminal?.data?.id; const device = await reqAdmin('/admin/devices', { method: 'POST', body: { device_code: `DEV-${ts}`, vendor: 'acme', model: 'v1', communication_mode: 'mqtt', status: 'active' }, _label: 'POST /admin/devices' }); const deviceId = device?.data?.id; await reqAdmin(`/admin/devices/${deviceId}/bind`, { method: 'POST', body: { merchant_id: merchantId, outlet_id: outletId, terminal_id: terminalId }, _label: 'POST /admin/devices/:id/bind' }); await reqDevice('/device/heartbeat', { method: 'POST', body: { device_id: deviceId, timestamp: new Date().toISOString(), firmware_version: '1.2.3', network_strength: 88, battery_level: 77, state: 'idle' }, _label: 'POST /device/heartbeat' }); const credential = await reqAdmin(`/admin/devices/${deviceId}/credentials/rotate`, { method: 'POST', body: {}, _label: 'POST /admin/devices/:id/credentials/rotate' }); const deviceSecret = credential?.data?.credential?.mqtt_password; if (!deviceSecret) { throw new Error('device credential rotate did not return one-time password'); } await reqDeviceCredential('/device/heartbeat', deviceId, deviceSecret, { method: 'POST', body: { device_id: deviceId, timestamp: new Date().toISOString(), firmware_version: '1.2.4', network_strength: 89, battery_level: 76, state: 'credential-auth' }, _label: 'POST /device/heartbeat credential auth' }); const tx = await reqAdmin('/admin/transactions', { method: 'POST', body: { partner_reference: `PR-${ts}`, merchant_id: merchantId, outlet_id: outletId, terminal_id: terminalId, device_id: deviceId, amount: 19900, currency: 'IDR', qr_mode: 'static', initiation_mode: 'static', status: 'initiated' }, _label: 'POST /admin/transactions' }); const txId = tx?.data?.id; const callback = { partner_reference: `PR-${ts}`, partner_txn_id: `PTX-${ts}`, amount: 19900, currency: 'IDR', payment_status: 'paid', status: 'paid', paid_at: new Date().toISOString() }; const signature = createHmac('sha256', SECRET).update(JSON.stringify(callback)).digest('hex'); await req('/integrations/qris/callback', { method: 'POST', headers: { 'X-Partner-Signature': signature }, body: { ...callback, signature }, _label: 'POST /integrations/qris/callback' }); await req('/integrations/qris/callback', { method: 'POST', headers: { 'X-Partner-Signature': signature }, body: { ...callback, signature }, _label: 'POST /integrations/qris/callback duplicate' }); await reqExpect('/integrations/qris/callback', 401, { method: 'POST', headers: { 'X-Partner-Signature': 'bad-signature' }, body: { ...callback, signature: 'bad-signature' }, _label: 'POST /integrations/qris/callback invalid signature' }); await reqAdmin(`/admin/transactions/${txId}`, { _label: 'GET /admin/transactions/:id' }); await reqAdmin(`/admin/transactions/${txId}/events`, { _label: 'GET /admin/transactions/:id/events' }); await reqAdmin(`/admin/ledger-entries?transaction_id=${txId}`, { _label: 'GET /admin/ledger-entries' }); const settlementBatchCreate = await reqAdmin('/admin/settlement-batches', { method: 'POST', body: { merchant_id: merchantId, cutoff_at: new Date().toISOString() }, _label: 'POST /admin/settlement-batches' }); const settlementBatch = settlementBatchCreate?.data?.batches?.[0]; if (!settlementBatch || settlementBatch.entry_count < 1 || Number(settlementBatch.net_payable_amount) <= 0) { throw new Error('settlement batch did not include merchant payable entries'); } await reqAdmin('/admin/settlement-batches?status=created', { _label: 'GET /admin/settlement-batches' }); await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}`, { _label: 'GET /admin/settlement-batches/:id' }); const settlementCsv = await req(`/admin/settlement-batches/${settlementBatch.id}/export.csv`, { headers: { Authorization: `Bearer ${ADMIN_TOKEN}` }, _label: 'GET /admin/settlement-batches/:id/export.csv' }); if (!String(settlementCsv).includes('batch_code,batch_status,merchant_id')) { throw new Error('settlement CSV export missing expected header'); } const settlementBankCsv = await req(`/admin/settlement-batches/${settlementBatch.id}/export.csv?format=bank_generic`, { headers: { Authorization: `Bearer ${ADMIN_TOKEN}` }, _label: 'GET /admin/settlement-batches/:id/export.csv bank_generic' }); if (!String(settlementBankCsv).includes('transfer_type,beneficiary_account_reference,beneficiary_account_type')) { throw new Error('settlement bank generic CSV export missing expected header'); } await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}/mark-paid`, { method: 'POST', body: { paid_at: new Date().toISOString(), paid_reference: `SMOKE-PAYOUT-${ts}`, paid_note: 'Smoke payout reconciliation note' }, _label: 'POST /admin/settlement-batches/:id/mark-paid' }); const settlementDetailWithEvents = await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}`, { _label: 'GET /admin/settlement-batches/:id events' }); const settlementEvents = settlementDetailWithEvents?.data?.events || []; if (!settlementEvents.some((event) => event.event_type === 'created') || !settlementEvents.some((event) => event.event_type === 'marked_paid')) { throw new Error('settlement payout event history missing created or marked_paid event'); } await reqExpect(`/admin/settlement-batches/${settlementBatch.id}/mark-paid`, 409, { method: 'POST', body: {}, headers: { Authorization: `Bearer ${ADMIN_TOKEN}` }, _label: 'POST /admin/settlement-batches/:id/mark-paid duplicate' }); await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}/reference`, { method: 'PATCH', body: { paid_reference: `SMOKE-PAYOUT-UPDATED-${ts}`, paid_note: 'Smoke payout reference correction' }, _label: 'PATCH /admin/settlement-batches/:id/reference' }); const settlementDetailAfterReference = await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}`, { _label: 'GET /admin/settlement-batches/:id reference-updated' }); if ( settlementDetailAfterReference?.data?.batch?.metadata_json?.paid_reference !== `SMOKE-PAYOUT-UPDATED-${ts}` || !settlementDetailAfterReference?.data?.events?.some((event) => event.event_type === 'reference_updated') ) { throw new Error('settlement reference update did not persist metadata or event history'); } await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}/adjustments`, { method: 'POST', body: { adjustment_type: 'debit', amount: 100, reason: 'Smoke reconciliation fee correction', note: 'Smoke adjustment event' }, _label: 'POST /admin/settlement-batches/:id/adjustments' }); const settlementDetailAfterAdjustment = await reqAdmin(`/admin/settlement-batches/${settlementBatch.id}`, { _label: 'GET /admin/settlement-batches/:id adjustment-recorded' }); if ( Number(settlementDetailAfterAdjustment?.data?.batch?.metadata_json?.total_adjustment_amount || 0) !== -100 || !Array.isArray(settlementDetailAfterAdjustment?.data?.adjustments) || settlementDetailAfterAdjustment.data.adjustments.length < 1 || Number(settlementDetailAfterAdjustment.data.adjustments[0]?.signed_amount || 0) !== -100 || !settlementDetailAfterAdjustment?.data?.events?.some((event) => event.event_type === 'adjustment_recorded') ) { throw new Error('settlement adjustment did not persist metadata or event history'); } const settlementAdjustmentReport = await reqAdmin(`/admin/settlement-adjustments?merchant_id=${merchantId}&limit=20`, { _label: 'GET /admin/settlement-adjustments' }); if ( !Array.isArray(settlementAdjustmentReport?.data?.rows) || settlementAdjustmentReport.data.rows.length < 1 || Number(settlementAdjustmentReport.data.signed_amount || 0) !== -100 || !settlementAdjustmentReport.data.rows.some((row) => row.batch_id === settlementBatch.id && Number(row.signed_amount || 0) === -100) ) { throw new Error('settlement adjustment report missing expected row or totals'); } const settlementAdjustmentCsv = await req(`/admin/settlement-adjustments/export.csv?merchant_id=${merchantId}&limit=20`, { headers: { Authorization: `Bearer ${ADMIN_TOKEN}` }, _label: 'GET /admin/settlement-adjustments/export.csv' }); if ( !String(settlementAdjustmentCsv).includes('adjustment_id,batch_id,batch_code') || !String(settlementAdjustmentCsv).includes('Smoke reconciliation fee correction') ) { throw new Error('settlement adjustment CSV export missing expected header or row'); } const merchantLogin = await req('/merchant/login', { method: 'POST', body: { username: merchantId, password: MERCHANT_PORTAL_PASSWORD }, _label: 'POST /merchant/login' }); if (merchantLogin?.data?.merchant?.id !== merchantId || !merchantLogin?.data?.token) { throw new Error('merchant login did not return expected merchant session'); } const merchantSummary = await reqMerchant('/merchant/settlement-summary', merchantId, { _label: 'GET /merchant/settlement-summary' }); const merchantPaidAmount = Number(merchantSummary?.data?.paid_amount || 0); const merchantAdjustmentAmount = Number(merchantSummary?.data?.adjustment_amount || 0); const merchantAdjustedPaidAmount = Number(merchantSummary?.data?.adjusted_paid_amount || 0); if ( merchantPaidAmount < Number(settlementBatch.net_payable_amount || 0) || merchantAdjustmentAmount !== -100 || Math.abs(merchantAdjustedPaidAmount - (merchantPaidAmount - 100)) > 0.01 ) { throw new Error('merchant settlement summary missing paid amount'); } await reqMerchant('/merchant/settlement-batches', merchantId, { _label: 'GET /merchant/settlement-batches' }); const merchantBatchDetail = await reqMerchant(`/merchant/settlement-batches/${settlementBatch.id}`, merchantId, { _label: 'GET /merchant/settlement-batches/:id' }); if (!merchantBatchDetail?.data?.events?.some((event) => event.event_type === 'marked_paid')) { throw new Error('merchant settlement batch detail missing payout event history'); } if (!Array.isArray(merchantBatchDetail?.data?.adjustments) || merchantBatchDetail.data.adjustments.length < 1) { throw new Error('merchant settlement batch detail missing formal adjustment rows'); } const merchantSettlementCsv = await req(`/merchant/settlement-batches/${settlementBatch.id}/export.csv`, { headers: { Authorization: `Bearer ${MERCHANT_TOKEN}`, 'X-Merchant-Id': merchantId }, _label: 'GET /merchant/settlement-batches/:id/export.csv' }); if (!String(merchantSettlementCsv).includes('batch_code,batch_status,merchant_id')) { throw new Error('merchant settlement CSV export missing expected header'); } const merchantSettlementBankCsv = await req(`/merchant/settlement-batches/${settlementBatch.id}/export.csv?format=bank_generic`, { headers: { Authorization: `Bearer ${MERCHANT_TOKEN}`, 'X-Merchant-Id': merchantId }, _label: 'GET /merchant/settlement-batches/:id/export.csv bank_generic' }); if (!String(merchantSettlementBankCsv).includes('transfer_type,beneficiary_account_reference,beneficiary_account_type')) { throw new Error('merchant settlement bank generic CSV export missing expected header'); } await reqAdmin(`/admin/audit-logs?entity_id=${txId}`, { _label: 'GET /admin/audit-logs' }); await reqAdmin(`/admin/transactions/${txId}/heartbeats`, { _label: 'GET /admin/transactions/:id/heartbeats' }); await reqAdmin(`/admin/devices/${deviceId}/heartbeats`, { _label: 'GET /admin/devices/:id/heartbeats' }); await reqAdmin(`/admin/devices/${deviceId}`, { _label: 'GET /admin/devices/:id health summary' }); await reqAdmin('/admin/notifications/failed', { _label: 'GET /admin/notifications/failed' }); await reqAdmin(`/admin/transactions/${txId}/retry-notification`, { method: 'POST', body: {}, _label: 'POST /admin/transactions/:id/retry-notification' }); const dashboardSummary = await reqAdmin('/admin/dashboard/summary', { _label: 'GET /admin/dashboard/summary' }); const dashboardData = dashboardSummary?.data || {}; const dashboardPaidAmount = Number(dashboardData.settlement_paid_amount || 0); const dashboardAdjustmentAmount = Number(dashboardData.settlement_adjustment_amount || 0); const dashboardAdjustedPaidAmount = Number(dashboardData.settlement_adjusted_paid_amount || 0); if ( dashboardPaidAmount < Number(settlementBatch.net_payable_amount || 0) || dashboardAdjustmentAmount !== -100 || Math.abs(dashboardAdjustedPaidAmount - (dashboardPaidAmount - 100)) > 0.01 || Number(dashboardData.settlement_paid_batches || 0) < 1 || Number(dashboardData.settlement_total_batches || 0) < 1 ) { throw new Error('dashboard settlement finance summary missing paid settlement aggregate'); } const noBindingOutlet = await reqAdmin(`/admin/merchants/${merchantId}/outlets`, { method: 'POST', body: { name: `No Binding Outlet ${ts}` }, _label: 'POST /admin/merchants/:id/outlets no-binding' }); const noBindingOutletId = noBindingOutlet?.data?.id; const noBindingTerminal = await reqAdmin(`/admin/outlets/${noBindingOutletId}/terminals`, { method: 'POST', body: { terminal_code: `TERM-NB-${ts}`, qr_mode: 'static' }, _label: 'POST /admin/outlets/:id/terminals no-binding' }); const noBindingTerminalId = noBindingTerminal?.data?.id; const noBindingTx = await reqAdmin('/admin/transactions', { method: 'POST', body: { partner_reference: `PR-NB-${ts}`, merchant_id: merchantId, outlet_id: noBindingOutletId, terminal_id: noBindingTerminalId, amount: 9900, currency: 'IDR', qr_mode: 'static', initiation_mode: 'static', status: 'initiated' }, _label: 'POST /admin/transactions no-binding' }); const noBindingCallback = { partner_reference: `PR-NB-${ts}`, partner_txn_id: `PTX-NB-${ts}`, amount: 9900, currency: 'IDR', payment_status: 'paid', status: 'paid', paid_at: new Date().toISOString() }; const noBindingSignature = createHmac('sha256', SECRET).update(JSON.stringify(noBindingCallback)).digest('hex'); await req('/integrations/qris/callback', { method: 'POST', headers: { 'X-Partner-Signature': noBindingSignature }, body: { ...noBindingCallback, signature: noBindingSignature }, _label: 'POST /integrations/qris/callback no-binding' }); await reqAdmin(`/admin/ledger-entries?transaction_id=${noBindingTx?.data?.id}`, { _label: 'GET /admin/ledger-entries no-binding' }); await reqAdmin('/admin/notifications/failed', { _label: 'GET /admin/notifications/failed no-binding' }); const failedBatchCreate = await reqAdmin('/admin/settlement-batches', { method: 'POST', body: { merchant_id: merchantId, cutoff_at: new Date().toISOString() }, _label: 'POST /admin/settlement-batches failed-case' }); const failedBatch = failedBatchCreate?.data?.batches?.[0]; if (!failedBatch) { throw new Error('failed settlement case did not create a batch'); } await reqAdmin(`/admin/settlement-batches/${failedBatch.id}/mark-failed`, { method: 'POST', body: { reason: 'Smoke payout rail rejected transfer', note: 'Smoke failed settlement lifecycle' }, _label: 'POST /admin/settlement-batches/:id/mark-failed' }); const failedBatchDetail = await reqAdmin(`/admin/settlement-batches/${failedBatch.id}`, { _label: 'GET /admin/settlement-batches/:id failed' }); if ( failedBatchDetail?.data?.batch?.status !== 'failed' || !failedBatchDetail?.data?.events?.some((event) => event.event_type === 'failed') ) { throw new Error('settlement failed lifecycle did not persist failed status/event'); } const reprocessedFailed = await reqAdmin(`/admin/settlement-batches/${failedBatch.id}/reprocess`, { method: 'POST', body: {}, _label: 'POST /admin/settlement-batches/:id/reprocess failed' }); const reprocessedFailedBatch = reprocessedFailed?.data?.new_batch; if (!reprocessedFailedBatch || reprocessedFailedBatch.status !== 'created') { throw new Error('failed settlement reprocess did not create a new created batch'); } const reprocessedFailedSource = await reqAdmin(`/admin/settlement-batches/${failedBatch.id}`, { _label: 'GET /admin/settlement-batches/:id reprocessed-source' }); if (!reprocessedFailedSource?.data?.events?.some((event) => event.event_type === 'reprocessed')) { throw new Error('failed settlement reprocess did not create source reprocessed event'); } await reqExpect(`/admin/settlement-batches/${failedBatch.id}/reprocess`, 409, { method: 'POST', body: {}, headers: { Authorization: `Bearer ${ADMIN_TOKEN}` }, _label: 'POST /admin/settlement-batches/:id/reprocess duplicate' }); const cancelTx = await reqAdmin('/admin/transactions', { method: 'POST', body: { partner_reference: `PR-CANCEL-${ts}`, merchant_id: merchantId, outlet_id: outletId, terminal_id: terminalId, amount: 7700, currency: 'IDR', qr_mode: 'static', initiation_mode: 'static', status: 'initiated' }, _label: 'POST /admin/transactions cancel-settlement' }); const cancelCallback = { partner_reference: `PR-CANCEL-${ts}`, partner_txn_id: `PTX-CANCEL-${ts}`, amount: 7700, currency: 'IDR', payment_status: 'paid', status: 'paid', paid_at: new Date().toISOString() }; const cancelSignature = createHmac('sha256', SECRET).update(JSON.stringify(cancelCallback)).digest('hex'); await req('/integrations/qris/callback', { method: 'POST', headers: { 'X-Partner-Signature': cancelSignature }, body: { ...cancelCallback, signature: cancelSignature }, _label: 'POST /integrations/qris/callback cancel-settlement' }); const cancelBatchCreate = await reqAdmin('/admin/settlement-batches', { method: 'POST', body: { merchant_id: merchantId, cutoff_at: new Date().toISOString() }, _label: 'POST /admin/settlement-batches cancel-case' }); const cancelBatch = cancelBatchCreate?.data?.batches?.[0]; if (!cancelBatch) { throw new Error('cancel settlement case did not create a batch'); } await reqAdmin(`/admin/settlement-batches/${cancelBatch.id}/cancel`, { method: 'POST', body: { reason: 'Smoke duplicate manual payout batch', note: `cancel tx ${cancelTx?.data?.id}` }, _label: 'POST /admin/settlement-batches/:id/cancel' }); const cancelBatchDetail = await reqAdmin(`/admin/settlement-batches/${cancelBatch.id}`, { _label: 'GET /admin/settlement-batches/:id cancelled' }); if ( cancelBatchDetail?.data?.batch?.status !== 'cancelled' || !cancelBatchDetail?.data?.events?.some((event) => event.event_type === 'cancelled') ) { throw new Error('settlement cancel lifecycle did not persist cancelled status/event'); } const reconciliationReport = await reqAdmin('/admin/reconciliation/settlement-batches?limit=50', { _label: 'GET /admin/reconciliation/settlement-batches' }); const reconciliationData = reconciliationReport?.data || {}; if ( !Array.isArray(reconciliationData.rows) || !Number.isFinite(Number(reconciliationData.total_batches)) || !Number.isFinite(Number(reconciliationData.mismatch_batches)) || Number(reconciliationData.total_batches) < 1 ) { throw new Error('settlement reconciliation report missing expected aggregate rows'); } const dynamicOutlet = await reqAdmin(`/admin/merchants/${merchantId}/outlets`, { method: 'POST', body: { name: `Dynamic API Outlet ${ts}` }, _label: 'POST /admin/merchants/:id/outlets dynamic-api' }); const dynamicOutletId = dynamicOutlet?.data?.id; const dynamicTerminal = await reqAdmin(`/admin/outlets/${dynamicOutletId}/terminals`, { method: 'POST', body: { terminal_code: `TERM-DYN-${ts}`, qr_mode: 'dynamic_api' }, _label: 'POST /admin/outlets/:id/terminals dynamic-api' }); const dynamicTerminalId = dynamicTerminal?.data?.id; const dynamicDevice = await reqAdmin('/admin/devices', { method: 'POST', body: { device_code: `DEV-API-${ts}`, vendor: 'acme', model: 'api-v1', communication_mode: 'api', capability_profile_json: { dynamic_qr: { api_direct: true, mqtt: false }, flows: ['dynamic_qr:api_direct', 'static_payment_notification'] }, status: 'active' }, _label: 'POST /admin/devices dynamic-api' }); const dynamicDeviceId = dynamicDevice?.data?.id; await reqAdmin(`/admin/devices/${dynamicDeviceId}/bind`, { method: 'POST', body: { merchant_id: merchantId, outlet_id: dynamicOutletId, terminal_id: dynamicTerminalId }, _label: 'POST /admin/devices/:id/bind dynamic-api' }); await reqExpect('/device/transactions/dynamic-qr', 400, { method: 'POST', headers: { Authorization: `Bearer ${DEVICE_TOKEN}` }, body: { device_id: deviceId, terminal_id: dynamicTerminalId, amount: 15000, currency: 'IDR', request_id: `DYN-STATIC-${ts}` }, _label: 'POST /device/transactions/dynamic-qr unsupported device' }); await reqExpect('/device/heartbeat', 403, { method: 'POST', headers: { 'X-Device-Id': deviceId, 'X-Device-Secret': deviceSecret }, body: { device_id: dynamicDeviceId, timestamp: new Date().toISOString(), network_strength: 80, battery_level: 70, state: 'wrong-device' }, _label: 'POST /device/heartbeat credential wrong device' }); const dynamicRequestId = `DYN-REQ-${ts}`; const dynamicQr = await reqDevice('/device/transactions/dynamic-qr', { method: 'POST', headers: { 'Idempotency-Key': dynamicRequestId }, body: { device_id: dynamicDeviceId, terminal_id: dynamicTerminalId, amount: 32100, currency: 'IDR', request_id: dynamicRequestId }, _label: 'POST /device/transactions/dynamic-qr' }); const dynamicQrReplay = await reqDevice('/device/transactions/dynamic-qr', { method: 'POST', headers: { 'Idempotency-Key': dynamicRequestId }, body: { device_id: dynamicDeviceId, terminal_id: dynamicTerminalId, amount: 32100, currency: 'IDR', request_id: dynamicRequestId }, _label: 'POST /device/transactions/dynamic-qr duplicate' }); if (dynamicQr?.data?.transaction_id !== dynamicQrReplay?.data?.transaction_id) { throw new Error('dynamic QR idempotency returned a different transaction'); } const dynamicCallback = { partner_reference: dynamicQr?.data?.partner_reference, partner_txn_id: `PTX-DYN-${ts}`, amount: 32100, currency: 'IDR', payment_status: 'paid', status: 'paid', paid_at: new Date().toISOString() }; const dynamicSignature = createHmac('sha256', SECRET).update(JSON.stringify(dynamicCallback)).digest('hex'); await req('/integrations/qris/callback', { method: 'POST', headers: { 'X-Partner-Signature': dynamicSignature }, body: { ...dynamicCallback, signature: dynamicSignature }, _label: 'POST /integrations/qris/callback dynamic-api' }); await reqAdmin(`/admin/transactions/${dynamicQr?.data?.transaction_id}`, { _label: 'GET /admin/transactions/:id dynamic-api' }); const dueDynamicTx = await reqAdmin('/admin/transactions', { method: 'POST', body: { partner_reference: `DUE-DYN-${ts}`, merchant_id: merchantId, outlet_id: dynamicOutletId, terminal_id: dynamicTerminalId, device_id: dynamicDeviceId, amount: 12000, currency: 'IDR', qr_mode: 'dynamic', initiation_mode: 'dynamic_api', status: 'awaiting_payment', expired_at: new Date(Date.now() - 60_000).toISOString() }, _label: 'POST /admin/transactions due dynamic' }); await reqAdmin('/admin/transactions/expire-due', { method: 'POST', body: { limit: 10 }, _label: 'POST /admin/transactions/expire-due' }); await reqAdmin(`/admin/transactions/${dueDynamicTx?.data?.id}`, { _label: 'GET /admin/transactions/:id expired dynamic' }); const mqttOutlet = await reqAdmin(`/admin/merchants/${merchantId}/outlets`, { method: 'POST', body: { name: `Dynamic MQTT Outlet ${ts}` }, _label: 'POST /admin/merchants/:id/outlets dynamic-mqtt' }); const mqttOutletId = mqttOutlet?.data?.id; const mqttTerminal = await reqAdmin(`/admin/outlets/${mqttOutletId}/terminals`, { method: 'POST', body: { terminal_code: `TERM-MQTT-${ts}`, qr_mode: 'dynamic_mqtt' }, _label: 'POST /admin/outlets/:id/terminals dynamic-mqtt' }); const mqttTerminalId = mqttTerminal?.data?.id; const mqttDevice = await reqAdmin('/admin/devices', { method: 'POST', body: { device_code: `DEV-MQTT-${ts}`, vendor: 'acme', model: 'mqtt-v1', communication_mode: 'mqtt', capability_profile_json: { dynamic_qr: { api_direct: false, mqtt: true }, flows: ['dynamic_qr:mqtt', 'static_payment_notification'] }, status: 'active' }, _label: 'POST /admin/devices dynamic-mqtt' }); const mqttDeviceId = mqttDevice?.data?.id; await reqAdmin(`/admin/devices/${mqttDeviceId}/bind`, { method: 'POST', body: { merchant_id: merchantId, outlet_id: mqttOutletId, terminal_id: mqttTerminalId }, _label: 'POST /admin/devices/:id/bind dynamic-mqtt' }); const mqttRequestId = `MQTT-DYN-${ts}`; const mqttQr = await reqDevice('/device/mqtt/uplink/dynamic-qr/request', { method: 'POST', body: { message_type: 'dynamic_qr_request', request_id: mqttRequestId, device_id: mqttDeviceId, terminal_id: mqttTerminalId, amount: 43200, currency: 'IDR', created_at: new Date().toISOString() }, _label: 'POST /device/mqtt/uplink/dynamic-qr/request' }); const mqttQrReplay = await reqDevice('/device/mqtt/uplink/dynamic-qr/request', { method: 'POST', body: { message_type: 'dynamic_qr_request', request_id: mqttRequestId, device_id: mqttDeviceId, terminal_id: mqttTerminalId, amount: 43200, currency: 'IDR', created_at: new Date().toISOString() }, _label: 'POST /device/mqtt/uplink/dynamic-qr/request duplicate' }); if (mqttQr?.data?.transaction_id !== mqttQrReplay?.data?.transaction_id) { throw new Error('MQTT dynamic QR idempotency returned a different transaction'); } await reqAdmin(`/admin/devices/${mqttDeviceId}/mqtt-messages?correlation_id=${mqttRequestId}`, { _label: 'GET /admin/devices/:id/mqtt-messages dynamic-mqtt' }); const mqttCallback = { partner_reference: mqttQr?.data?.partner_reference, partner_txn_id: `PTX-MQTT-${ts}`, amount: 43200, currency: 'IDR', payment_status: 'paid', status: 'paid', paid_at: new Date().toISOString() }; const mqttSignature = createHmac('sha256', SECRET).update(JSON.stringify(mqttCallback)).digest('hex'); await req('/integrations/qris/callback', { method: 'POST', headers: { 'X-Partner-Signature': mqttSignature }, body: { ...mqttCallback, signature: mqttSignature }, _label: 'POST /integrations/qris/callback dynamic-mqtt' }); await reqAdmin(`/admin/transactions/${mqttQr?.data?.transaction_id}`, { _label: 'GET /admin/transactions/:id dynamic-mqtt' }); const pushedConfig = await reqAdmin(`/admin/devices/${dynamicDeviceId}/config`, { method: 'PATCH', body: { settings: { volume: 65, language: 'id-ID', heartbeat_interval_seconds: 45 } }, _label: 'PATCH /admin/devices/:id/config' }); const configVersion = pushedConfig?.data?.config?.config_version; await reqAdmin(`/admin/devices/${dynamicDeviceId}/config/status`, { _label: 'GET /admin/devices/:id/config/status pending' }); await reqAdmin(`/admin/devices/${dynamicDeviceId}/config/retry-push`, { method: 'POST', body: {}, _label: 'POST /admin/devices/:id/config/retry-push' }); await reqDevice(`/device/config?device_id=${dynamicDeviceId}`, { _label: 'GET /device/config' }); await reqDevice('/device/config/ack', { method: 'POST', body: { device_id: dynamicDeviceId, config_version: configVersion, status: 'applied', result_payload: { applied_at: new Date().toISOString() } }, _label: 'POST /device/config/ack' }); await reqAdmin(`/admin/devices/${dynamicDeviceId}/config`, { _label: 'GET /admin/devices/:id/config' }); await reqAdmin(`/admin/devices/${dynamicDeviceId}/config/status`, { _label: 'GET /admin/devices/:id/config/status applied' }); await reqExpect(`/admin/devices/${dynamicDeviceId}/config/retry-push`, 409, { method: 'POST', headers: { Authorization: `Bearer ${ADMIN_TOKEN}` }, body: {}, _label: 'POST /admin/devices/:id/config/retry-push already applied' }); await reqAdmin(`/admin/devices/${dynamicDeviceId}/mqtt-messages?message_type=config_push`, { _label: 'GET /admin/devices/:id/mqtt-messages config' }); await reqAdmin(`/admin/devices/${dynamicDeviceId}/mqtt-messages?message_type=config_ack`, { _label: 'GET /admin/devices/:id/mqtt-messages config ack' }); console.log(`Smoke point 4 flow done. tx=${txId} device=${deviceId}`); })();