|
|
|
@ -207,6 +207,30 @@ app.post('/api/payments/charge', async (req, res) => {
|
|
|
|
const pt = req?.body?.payment_type
|
|
|
|
const pt = req?.body?.payment_type
|
|
|
|
logInfo('charge.request', { id: req.id, payment_type: pt })
|
|
|
|
logInfo('charge.request', { id: req.id, payment_type: pt })
|
|
|
|
logDebug('charge.payload', maskPayload(req.body))
|
|
|
|
logDebug('charge.payload', maskPayload(req.body))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Idempotency guard: if an order is already pending in Midtrans, block re-charge for the same order_id
|
|
|
|
|
|
|
|
const orderId = req?.body?.transaction_details?.order_id || req?.body?.order_id || ''
|
|
|
|
|
|
|
|
if (orderId) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const st = await core.transaction.status(orderId)
|
|
|
|
|
|
|
|
const ts = (st?.transaction_status || '').toLowerCase()
|
|
|
|
|
|
|
|
if (ts === 'pending') {
|
|
|
|
|
|
|
|
logWarn('charge.blocked.pending_exists', { id: req.id, order_id: orderId })
|
|
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
|
|
error: 'ORDER_ACTIVE',
|
|
|
|
|
|
|
|
message: 'Order sudah memiliki transaksi pending di Midtrans; tidak dapat membuat ulang. Gunakan instruksi pembayaran yang ada atau buat order baru.',
|
|
|
|
|
|
|
|
status: st,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
const msg = (e?.message || '').toLowerCase()
|
|
|
|
|
|
|
|
if (msg.includes('not found') || msg.includes('404')) {
|
|
|
|
|
|
|
|
logDebug('charge.status_not_found', { order_id: orderId })
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logDebug('charge.status_check_error', { order_id: orderId, message: e?.message })
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
const isBankType = pt === 'bank_transfer' || pt === 'echannel' || pt === 'permata'
|
|
|
|
const isBankType = pt === 'bank_transfer' || pt === 'echannel' || pt === 'permata'
|
|
|
|
if (isBankType && !ENABLE.bank_transfer) {
|
|
|
|
if (isBankType && !ENABLE.bank_transfer) {
|
|
|
|
logWarn('charge.blocked', { id: req.id, reason: 'bank_transfer disabled' })
|
|
|
|
logWarn('charge.blocked', { id: req.id, reason: 'bank_transfer disabled' })
|
|
|
|
@ -251,7 +275,7 @@ app.get('/api/payments/:orderId/status', async (req, res) => {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// External ERP Create Transaction → issue payment link
|
|
|
|
// External ERP Create Transaction → issue payment link
|
|
|
|
app.post('/createtransaksi', (req, res) => {
|
|
|
|
app.post('/createtransaksi', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if (!verifyExternalKey(req)) {
|
|
|
|
if (!verifyExternalKey(req)) {
|
|
|
|
logWarn('createtransaksi.unauthorized', { id: req.id })
|
|
|
|
logWarn('createtransaksi.unauthorized', { id: req.id })
|
|
|
|
@ -268,7 +292,12 @@ app.post('/createtransaksi', (req, res) => {
|
|
|
|
const nominalRaw = req?.body?.nominal
|
|
|
|
const nominalRaw = req?.body?.nominal
|
|
|
|
const items = Array.isArray(req?.body?.item) ? req.body.item : []
|
|
|
|
const items = Array.isArray(req?.body?.item) ? req.body.item : []
|
|
|
|
const primaryItemId = items?.[0]?.item_id
|
|
|
|
const primaryItemId = items?.[0]?.item_id
|
|
|
|
const order_id = String(primaryItemId || mercantId || req?.body?.order_id || req?.body?.item_id || '')
|
|
|
|
// Mapping order_id: gunakan "mercant_id:item_id" bila keduanya tersedia,
|
|
|
|
|
|
|
|
// jika tidak, fallback ke item_id atau mercant_id atau field order_id/item_id yang disediakan.
|
|
|
|
|
|
|
|
const order_id = String(
|
|
|
|
|
|
|
|
(primaryItemId && mercantId) ? `${mercantId}:${primaryItemId}` :
|
|
|
|
|
|
|
|
(primaryItemId || mercantId || req?.body?.order_id || req?.body?.item_id || '')
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Bentuk customer dari field nama/no_telepon/email
|
|
|
|
// Bentuk customer dari field nama/no_telepon/email
|
|
|
|
const customer = {
|
|
|
|
const customer = {
|
|
|
|
@ -299,6 +328,28 @@ app.post('/createtransaksi', (req, res) => {
|
|
|
|
return res.status(409).json({ error: 'ORDER_ACTIVE', message: 'Active payment link exists' })
|
|
|
|
return res.status(409).json({ error: 'ORDER_ACTIVE', message: 'Active payment link exists' })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Guard tambahan: cek ke Midtrans apakah order_id sudah memiliki transaksi pending
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const status = await core.transaction.status(order_id)
|
|
|
|
|
|
|
|
const s = (status?.transaction_status || '').toLowerCase()
|
|
|
|
|
|
|
|
if (s === 'pending') {
|
|
|
|
|
|
|
|
logWarn('createtransaksi.midtrans_pending', { order_id })
|
|
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
|
|
error: 'ORDER_ACTIVE',
|
|
|
|
|
|
|
|
message: 'Order sudah memiliki transaksi pending di Midtrans; gunakan instruksi pembayaran yang ada atau buat order baru.',
|
|
|
|
|
|
|
|
status: { order_id: status?.order_id, status_code: status?.status_code, status_message: status?.status_message, payment_type: status?.payment_type },
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
// Jika 404/not found, lanjut membuat payment link; error lain tetap diteruskan
|
|
|
|
|
|
|
|
const msg = (e?.message || '').toLowerCase()
|
|
|
|
|
|
|
|
if (msg.includes('not found') || msg.includes('404')) {
|
|
|
|
|
|
|
|
logDebug('createtransaksi.midtrans_status_not_found', { order_id })
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logDebug('createtransaksi.midtrans_status_check_error', { order_id, message: e?.message })
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const token = createPaymentLinkToken({ order_id, nominal, expire_at, customer, allowed_methods })
|
|
|
|
const token = createPaymentLinkToken({ order_id, nominal, expire_at, customer, allowed_methods })
|
|
|
|
const url = `${PAYMENT_LINK_BASE}/${token}`
|
|
|
|
const url = `${PAYMENT_LINK_BASE}/${token}`
|
|
|
|
activeOrders.set(order_id, expire_at)
|
|
|
|
activeOrders.set(order_id, expire_at)
|
|
|
|
|