kua-cashier/matcher.js

60 lines
1.9 KiB
JavaScript

import { query } from './db.js';
import { normalizeRut } from './util.js';
// Extract a 6-char uppercase alphanumeric code from transfer description
function extractCode(description) {
if (!description) return null;
const m = description.toUpperCase().match(/\b([A-Z0-9]{6})\b/);
return m ? m[1] : null;
}
/**
* Try to match an inbound payment against a pending intent.
* Returns { intent, confidence } or null.
*
* Priority:
* 1. Reference code in description — strongest signal, unambiguous
* 2. Exact amount + sender RUT — reliable when RUT is available
* 3. Exact amount only (same account) — weaker, flagged as 'amount_only'
*/
export async function matchInbound(inbound) {
// 1. Reference code
const code = extractCode(inbound.description);
if (code) {
const { rows } = await query(
`SELECT * FROM payment_intents WHERE reference_code = $1 AND status = 'pending'`,
[code]
);
if (rows[0]) return { intent: rows[0], confidence: 'reference_code' };
}
// 2. Exact amount + sender RUT
const senderRut = normalizeRut(inbound.sender_rut);
if (senderRut) {
const { rows } = await query(
`SELECT * FROM payment_intents
WHERE exact_amount = $1
AND status = 'pending'
AND (
sender_rut IS NULL
OR replace(lower(sender_rut), '.', '') = $2
)
AND ($3::text IS NULL OR account_id = $3)`,
[inbound.amount, senderRut, inbound.account_id ?? null]
);
if (rows[0]) return { intent: rows[0], confidence: 'exact_amount_rut' };
}
// 3. Exact amount on same account (weakest — no RUT confirmation)
if (inbound.account_id) {
const { rows } = await query(
`SELECT * FROM payment_intents
WHERE exact_amount = $1 AND account_id = $2 AND status = 'pending'`,
[inbound.amount, inbound.account_id]
);
if (rows[0]) return { intent: rows[0], confidence: 'amount_only' };
}
return null;
}