feat: improve payments
This commit is contained in:
parent
371eeffd38
commit
7b8f693710
|
|
@ -22,7 +22,7 @@ export async function migrate() {
|
||||||
coin TEXT NOT NULL,
|
coin TEXT NOT NULL,
|
||||||
amount_crypto TEXT NOT NULL,
|
amount_crypto TEXT NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
derivation_index INTEGER NOT NULL UNIQUE,
|
derivation_index INTEGER NOT NULL,
|
||||||
status TEXT NOT NULL DEFAULT 'pending',
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
paid_at TIMESTAMPTZ,
|
paid_at TIMESTAMPTZ,
|
||||||
|
|
@ -52,5 +52,9 @@ export async function migrate() {
|
||||||
|
|
||||||
await sql`ALTER TABLE payments ADD COLUMN IF NOT EXISTS receipt_html TEXT`;
|
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");
|
console.log("Pay DB ready");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,15 @@ async function refreshMaps() {
|
||||||
|
|
||||||
// ── Core logic: one place for all state transitions ─────────────────
|
// ── 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`
|
const [ins] = await sql`
|
||||||
INSERT INTO payment_txs (payment_id, txid, amount, confirmed)
|
INSERT INTO payment_txs (payment_id, txid, amount, confirmed)
|
||||||
VALUES (${paymentId}, ${txid}, ${amount.toFixed(8)}, ${confirmed})
|
VALUES (${paymentId}, ${txid}, ${amount.toFixed(8)}, ${confirmed})
|
||||||
|
|
@ -111,7 +119,7 @@ async function handleTxEvent(event: any) {
|
||||||
if (txValue <= 0) continue;
|
if (txValue <= 0) continue;
|
||||||
|
|
||||||
console.log(`SSE: tx ${txHash} for payment ${payment.id}: +${txValue} ${payment.coin}`);
|
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);
|
txidToPayment.set(txHash, payment.id);
|
||||||
await evaluatePayment(payment.id);
|
await evaluatePayment(payment.id);
|
||||||
return;
|
return;
|
||||||
|
|
@ -197,7 +205,7 @@ export async function checkPayments() {
|
||||||
// Sync txs from address API
|
// Sync txs from address API
|
||||||
for (const tx of info.in ?? []) {
|
for (const tx of info.in ?? []) {
|
||||||
if (!tx.txid) continue;
|
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);
|
await evaluatePayment(payment.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,9 @@ export const routes = new Elysia()
|
||||||
// Crypto amount with 8 decimal precision
|
// Crypto amount with 8 decimal precision
|
||||||
const amountCrypto = (amountUsd / rate).toFixed(8);
|
const amountCrypto = (amountUsd / rate).toFixed(8);
|
||||||
|
|
||||||
// Get next derivation index
|
// Get next derivation index for this coin
|
||||||
const [{ next_index }] = await sql`
|
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
|
// Derive address
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ nuke_db() {
|
||||||
fi
|
fi
|
||||||
echo "[nuke-db] Dropping all tables on database-eu-central..."
|
echo "[nuke-db] Dropping all tables on database-eu-central..."
|
||||||
$SSH $DB_HOST bash << 'REMOTE'
|
$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"
|
echo "All tables dropped"
|
||||||
REMOTE
|
REMOTE
|
||||||
echo "[nuke-db] Done. Tables will be recreated on next API/web restart."
|
echo "[nuke-db] Done. Tables will be recreated on next API/web restart."
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue