From 7b8f6937104d9eae966b8c7b79feec58aed7077b Mon Sep 17 00:00:00 2001 From: nate Date: Tue, 24 Mar 2026 21:17:50 +0400 Subject: [PATCH] feat: improve payments --- apps/pay/src/db.ts | 6 +++++- apps/pay/src/monitor.ts | 14 +++++++++++--- apps/pay/src/routes.ts | 4 ++-- deploy.sh | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/pay/src/db.ts b/apps/pay/src/db.ts index 24cf074..0143153 100644 --- a/apps/pay/src/db.ts +++ b/apps/pay/src/db.ts @@ -22,7 +22,7 @@ export async function migrate() { coin TEXT NOT NULL, amount_crypto TEXT NOT NULL, address TEXT NOT NULL, - derivation_index INTEGER NOT NULL UNIQUE, + derivation_index INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TIMESTAMPTZ DEFAULT now(), paid_at TIMESTAMPTZ, @@ -52,5 +52,9 @@ export async function migrate() { await sql`ALTER TABLE payments ADD COLUMN IF NOT EXISTS receipt_html TEXT`; + // Derivation index should be unique per coin, not globally + await sql`DROP INDEX IF EXISTS payments_derivation_index_key`; + await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_payments_coin_derivation ON payments(coin, derivation_index)`; + console.log("Pay DB ready"); } diff --git a/apps/pay/src/monitor.ts b/apps/pay/src/monitor.ts index 1cd9bb2..522c474 100644 --- a/apps/pay/src/monitor.ts +++ b/apps/pay/src/monitor.ts @@ -37,7 +37,15 @@ async function refreshMaps() { // ── Core logic: one place for all state transitions ───────────────── -async function recordTx(paymentId: number, txid: string, amount: number, confirmed: boolean) { +async function recordTx(paymentId: number, address: string, txid: string, amount: number, confirmed: boolean) { + // Verify the payment exists and the address matches — prevents stale in-memory state + // from attributing transactions to the wrong payment + const [payment] = await sql` + SELECT id FROM payments WHERE id = ${paymentId} AND address = ${address} + AND status IN ('pending', 'underpaid', 'confirming') + `; + if (!payment) return; + const [ins] = await sql` INSERT INTO payment_txs (payment_id, txid, amount, confirmed) VALUES (${paymentId}, ${txid}, ${amount.toFixed(8)}, ${confirmed}) @@ -111,7 +119,7 @@ async function handleTxEvent(event: any) { if (txValue <= 0) continue; console.log(`SSE: tx ${txHash} for payment ${payment.id}: +${txValue} ${payment.coin}`); - await recordTx(payment.id, txHash, txValue, false); + await recordTx(payment.id, payment.address, txHash, txValue, false); txidToPayment.set(txHash, payment.id); await evaluatePayment(payment.id); return; @@ -197,7 +205,7 @@ export async function checkPayments() { // Sync txs from address API for (const tx of info.in ?? []) { if (!tx.txid) continue; - await recordTx(payment.id, tx.txid, Number(tx.amount ?? 0), tx.block != null); + await recordTx(payment.id, payment.address, tx.txid, Number(tx.amount ?? 0), tx.block != null); } await evaluatePayment(payment.id); } catch (e) { diff --git a/apps/pay/src/routes.ts b/apps/pay/src/routes.ts index 5dac983..60be715 100644 --- a/apps/pay/src/routes.ts +++ b/apps/pay/src/routes.ts @@ -89,9 +89,9 @@ export const routes = new Elysia() // Crypto amount with 8 decimal precision const amountCrypto = (amountUsd / rate).toFixed(8); - // Get next derivation index + // Get next derivation index for this coin const [{ next_index }] = await sql` - SELECT COALESCE(MAX(derivation_index), -1) + 1 as next_index FROM payments + SELECT COALESCE(MAX(derivation_index), -1) + 1 as next_index FROM payments WHERE coin = ${coin} `; // Derive address diff --git a/deploy.sh b/deploy.sh index bfd8f87..dea7c56 100755 --- a/deploy.sh +++ b/deploy.sh @@ -29,7 +29,7 @@ nuke_db() { fi echo "[nuke-db] Dropping all tables on database-eu-central..." $SSH $DB_HOST bash << 'REMOTE' - sudo -u postgres psql -d pingql -c "DROP TABLE IF EXISTS payments, pings, api_keys, monitors, accounts CASCADE;" + sudo -u postgres psql -d pingql -c "DROP TABLE IF EXISTS payment_txs, ping_bodies, payments, pings, api_keys, monitors, accounts CASCADE;" echo "All tables dropped" REMOTE echo "[nuke-db] Done. Tables will be recreated on next API/web restart."