update: .......
This commit is contained in:
parent
955b26f942
commit
e62b60e0fd
|
|
@ -33,6 +33,20 @@ export async function migrate() {
|
||||||
|
|
||||||
await sql`ALTER TABLE payments ADD COLUMN IF NOT EXISTS amount_received TEXT NOT NULL DEFAULT '0'`;
|
await sql`ALTER TABLE payments ADD COLUMN IF NOT EXISTS amount_received TEXT NOT NULL DEFAULT '0'`;
|
||||||
|
|
||||||
|
await sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS payment_txs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
payment_id BIGINT NOT NULL REFERENCES payments(id) ON DELETE CASCADE,
|
||||||
|
txid TEXT NOT NULL,
|
||||||
|
amount TEXT NOT NULL,
|
||||||
|
confirmed BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
detected_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
UNIQUE(payment_id, txid)
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
await sql`CREATE INDEX IF NOT EXISTS idx_payment_txs_payment ON payment_txs(payment_id)`;
|
||||||
|
await sql`CREATE INDEX IF NOT EXISTS idx_payment_txs_txid ON payment_txs(txid)`;
|
||||||
await sql`CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status)`;
|
await sql`CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status)`;
|
||||||
await sql`CREATE INDEX IF NOT EXISTS idx_payments_account ON payments(account_id)`;
|
await sql`CREATE INDEX IF NOT EXISTS idx_payments_account ON payments(account_id)`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import { getAddressInfo, getAddressInfoBulk } from "./freedom";
|
||||||
import { COINS } from "./plans";
|
import { COINS } from "./plans";
|
||||||
|
|
||||||
const SOCK_API = process.env.FREEDOM_SOCK ?? "https://sock-v1.freedom.st";
|
const SOCK_API = process.env.FREEDOM_SOCK ?? "https://sock-v1.freedom.st";
|
||||||
const THRESHOLD = 0.995; // 0.5% tolerance for network fees
|
const THRESHOLD = 0.995;
|
||||||
|
|
||||||
// ── In-memory maps ──────────────────────────────────────────────────
|
// ── In-memory maps ──────────────────────────────────────────────────
|
||||||
let addressMap = new Map<string, any>();
|
let addressMap = new Map<string, any>(); // address → payment
|
||||||
let txidLookup = new Map<string, number>(); // txid → payment.id
|
let txidToPayment = new Map<string, number>(); // txid → payment.id
|
||||||
const seenTxids = new Set<string>();
|
const seenTxids = new Set<string>();
|
||||||
|
|
||||||
async function refreshMaps() {
|
async function refreshMaps() {
|
||||||
|
|
@ -21,21 +21,24 @@ async function refreshMaps() {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const newAddr = new Map<string, any>();
|
const newAddr = new Map<string, any>();
|
||||||
const newTxidLookup = new Map<string, number>();
|
const newTxid = new Map<string, number>();
|
||||||
|
|
||||||
for (const p of active) {
|
for (const p of active) {
|
||||||
newAddr.set(p.address, p);
|
newAddr.set(p.address, p);
|
||||||
if (p.txid) {
|
|
||||||
// txid column may contain comma-separated txids
|
|
||||||
for (const t of p.txid.split(",")) newTxidLookup.set(t, p.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load all txids for active payments
|
||||||
|
if (active.length > 0) {
|
||||||
|
const ids = active.map((p: any) => p.id);
|
||||||
|
const txs = await sql`SELECT payment_id, txid FROM payment_txs WHERE payment_id = ANY(${ids})`;
|
||||||
|
for (const tx of txs) newTxid.set(tx.txid, tx.payment_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
addressMap = newAddr;
|
addressMap = newAddr;
|
||||||
txidLookup = newTxidLookup;
|
txidToPayment = newTxid;
|
||||||
|
|
||||||
for (const txid of seenTxids) {
|
for (const txid of seenTxids) {
|
||||||
if (!newTxidLookup.has(txid)) seenTxids.delete(txid);
|
if (!newTxid.has(txid)) seenTxids.delete(txid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,53 +111,49 @@ async function handleTxEvent(event: any) {
|
||||||
const coin = COINS[payment.coin];
|
const coin = COINS[payment.coin];
|
||||||
if (!coin) continue;
|
if (!coin) continue;
|
||||||
|
|
||||||
// Sum output value going to our address in this tx
|
// Sum outputs going to our address
|
||||||
const txValue = outputs
|
const txValue = outputs
|
||||||
.filter((o: any) => o?.script?.address === addr)
|
.filter((o: any) => o?.script?.address === addr)
|
||||||
.reduce((sum: number, o: any) => sum + Number(o.value ?? 0), 0);
|
.reduce((sum: number, o: any) => sum + Number(o.value ?? 0), 0);
|
||||||
|
|
||||||
const prevReceived = parseFloat(payment.amount_received || "0");
|
if (txValue <= 0) continue;
|
||||||
const newReceived = prevReceived + txValue;
|
|
||||||
|
console.log(`SSE: tx ${txHash} for payment ${payment.id}: +${txValue} ${payment.coin}`);
|
||||||
|
|
||||||
|
// Insert into payment_txs (ignore duplicate)
|
||||||
|
await sql`
|
||||||
|
INSERT INTO payment_txs (payment_id, txid, amount)
|
||||||
|
VALUES (${payment.id}, ${txHash}, ${txValue.toFixed(8)})
|
||||||
|
ON CONFLICT (payment_id, txid) DO NOTHING
|
||||||
|
`;
|
||||||
|
txidToPayment.set(txHash, payment.id);
|
||||||
|
|
||||||
|
// Recalculate total received
|
||||||
|
const [{ total }] = await sql`
|
||||||
|
SELECT COALESCE(SUM(amount::numeric), 0) as total FROM payment_txs WHERE payment_id = ${payment.id}
|
||||||
|
`;
|
||||||
|
const totalReceived = Number(total);
|
||||||
const expected = parseFloat(payment.amount_crypto);
|
const expected = parseFloat(payment.amount_crypto);
|
||||||
const threshold = expected * THRESHOLD;
|
const threshold = expected * THRESHOLD;
|
||||||
|
|
||||||
console.log(`SSE: tx ${txHash} for payment ${payment.id}: +${txValue} ${payment.coin} (total: ${newReceived}/${expected})`);
|
if (coin.confirmations === 0 && totalReceived >= threshold) {
|
||||||
|
await sql`UPDATE payments SET amount_received = ${totalReceived.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
||||||
// Append txid
|
|
||||||
const txids = payment.txid ? payment.txid + "," + txHash : txHash;
|
|
||||||
|
|
||||||
if (coin.confirmations === 0 && newReceived >= threshold) {
|
|
||||||
// 0-conf, full amount: activate immediately
|
|
||||||
await sql`UPDATE payments SET amount_received = ${newReceived.toFixed(8)}, txid = ${txids}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
|
||||||
await applyPlan(payment);
|
await applyPlan(payment);
|
||||||
addressMap.delete(addr);
|
addressMap.delete(addr);
|
||||||
console.log(`Payment ${payment.id} paid (0-conf)`);
|
console.log(`Payment ${payment.id} paid (0-conf)`);
|
||||||
} else if (coin.confirmations === 0 && newReceived > 0) {
|
} else if (totalReceived >= threshold) {
|
||||||
// 0-conf, partial: underpaid
|
await sql`UPDATE payments SET amount_received = ${totalReceived.toFixed(8)}, status = 'confirming' WHERE id = ${payment.id}`;
|
||||||
await sql`UPDATE payments SET amount_received = ${newReceived.toFixed(8)}, txid = ${txids}, status = 'underpaid' WHERE id = ${payment.id}`;
|
payment.amount_received = totalReceived.toFixed(8);
|
||||||
payment.amount_received = newReceived.toFixed(8);
|
|
||||||
payment.txid = txids;
|
|
||||||
payment.status = "underpaid";
|
|
||||||
console.log(`Payment ${payment.id} underpaid (0-conf): ${newReceived}/${expected}`);
|
|
||||||
} else if (newReceived >= threshold) {
|
|
||||||
// 1+ conf, full amount: confirming
|
|
||||||
await sql`UPDATE payments SET amount_received = ${newReceived.toFixed(8)}, txid = ${txids}, status = 'confirming' WHERE id = ${payment.id}`;
|
|
||||||
payment.amount_received = newReceived.toFixed(8);
|
|
||||||
payment.txid = txids;
|
|
||||||
payment.status = "confirming";
|
payment.status = "confirming";
|
||||||
for (const t of txids.split(",")) txidLookup.set(t, payment.id);
|
console.log(`Payment ${payment.id} confirming: ${totalReceived}/${expected}`);
|
||||||
console.log(`Payment ${payment.id} confirming: ${newReceived}/${expected}`);
|
|
||||||
} else {
|
} else {
|
||||||
// 1+ conf, partial: underpaid
|
await sql`UPDATE payments SET amount_received = ${totalReceived.toFixed(8)}, status = 'underpaid' WHERE id = ${payment.id}`;
|
||||||
await sql`UPDATE payments SET amount_received = ${newReceived.toFixed(8)}, txid = ${txids}, status = 'underpaid' WHERE id = ${payment.id}`;
|
payment.amount_received = totalReceived.toFixed(8);
|
||||||
payment.amount_received = newReceived.toFixed(8);
|
|
||||||
payment.txid = txids;
|
|
||||||
payment.status = "underpaid";
|
payment.status = "underpaid";
|
||||||
for (const t of txids.split(",")) txidLookup.set(t, payment.id);
|
console.log(`Payment ${payment.id} underpaid: ${totalReceived}/${expected}`);
|
||||||
console.log(`Payment ${payment.id} underpaid: ${newReceived}/${expected}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return; // Only process first matching output set per tx
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,43 +163,47 @@ async function handleBlockEvent(event: any) {
|
||||||
const blockTxs: string[] = event.data?.tx ?? [];
|
const blockTxs: string[] = event.data?.tx ?? [];
|
||||||
if (blockTxs.length === 0) return;
|
if (blockTxs.length === 0) return;
|
||||||
|
|
||||||
// Find payments with txids in this block
|
// Find payment txs in this block
|
||||||
const paymentIds = new Set<number>();
|
const paymentIds = new Set<number>();
|
||||||
for (const txid of blockTxs) {
|
for (const txid of blockTxs) {
|
||||||
const pid = txidLookup.get(txid);
|
const pid = txidToPayment.get(txid);
|
||||||
if (pid != null) paymentIds.add(pid);
|
if (pid != null) paymentIds.add(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paymentIds.size === 0) return;
|
if (paymentIds.size === 0) return;
|
||||||
|
|
||||||
|
// Mark matched txs as confirmed
|
||||||
|
const matchedTxids = blockTxs.filter(t => txidToPayment.has(t));
|
||||||
|
if (matchedTxids.length > 0) {
|
||||||
|
await sql`UPDATE payment_txs SET confirmed = true WHERE txid = ANY(${matchedTxids})`;
|
||||||
|
}
|
||||||
|
|
||||||
for (const pid of paymentIds) {
|
for (const pid of paymentIds) {
|
||||||
// Re-fetch from DB for latest state
|
|
||||||
const [payment] = await sql`
|
const [payment] = await sql`
|
||||||
SELECT * FROM payments WHERE id = ${pid} AND status IN ('underpaid', 'confirming')
|
SELECT * FROM payments WHERE id = ${pid} AND status IN ('underpaid', 'confirming')
|
||||||
`;
|
`;
|
||||||
if (!payment) continue;
|
if (!payment) continue;
|
||||||
|
|
||||||
// Check confirmed amount via address API
|
// Check if all txs for this payment are confirmed
|
||||||
let info: any;
|
const [{ unconfirmed }] = await sql`
|
||||||
try { info = await getAddressInfo(payment.address); } catch { continue; }
|
SELECT COUNT(*)::int as unconfirmed FROM payment_txs
|
||||||
if (!info || info.error) continue;
|
WHERE payment_id = ${pid} AND confirmed = false
|
||||||
|
`;
|
||||||
|
|
||||||
const receivedConfirmed = Number(info.received_confirmed ?? 0);
|
if (Number(unconfirmed) > 0) continue;
|
||||||
|
|
||||||
|
// All confirmed — check total meets threshold
|
||||||
|
const [{ total }] = await sql`
|
||||||
|
SELECT COALESCE(SUM(amount::numeric), 0) as total FROM payment_txs WHERE payment_id = ${pid}
|
||||||
|
`;
|
||||||
|
const totalReceived = Number(total);
|
||||||
const expected = parseFloat(payment.amount_crypto);
|
const expected = parseFloat(payment.amount_crypto);
|
||||||
const threshold = expected * THRESHOLD;
|
|
||||||
|
|
||||||
if (receivedConfirmed >= threshold) {
|
if (totalReceived >= expected * THRESHOLD) {
|
||||||
await sql`UPDATE payments SET amount_received = ${receivedConfirmed.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
await sql`UPDATE payments SET amount_received = ${totalReceived.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${pid} AND status != 'paid'`;
|
||||||
await applyPlan(payment);
|
await applyPlan(payment);
|
||||||
addressMap.delete(payment.address);
|
addressMap.delete(payment.address);
|
||||||
// Clean up txid lookups
|
console.log(`Payment ${pid} paid (all txs confirmed)`);
|
||||||
if (payment.txid) {
|
|
||||||
for (const t of payment.txid.split(",")) txidLookup.delete(t);
|
|
||||||
}
|
|
||||||
console.log(`Payment ${payment.id} paid (confirmed)`);
|
|
||||||
} else if (receivedConfirmed > 0) {
|
|
||||||
// Partially confirmed — update amount_received
|
|
||||||
await sql`UPDATE payments SET amount_received = ${receivedConfirmed.toFixed(8)} WHERE id = ${payment.id}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -247,22 +250,42 @@ export async function checkPayments() {
|
||||||
const receivedConfirmed = Number(info.received_confirmed ?? 0);
|
const receivedConfirmed = Number(info.received_confirmed ?? 0);
|
||||||
const expected = parseFloat(payment.amount_crypto);
|
const expected = parseFloat(payment.amount_crypto);
|
||||||
const threshold = expected * THRESHOLD;
|
const threshold = expected * THRESHOLD;
|
||||||
const txid = payment.txid || findTxid(info);
|
|
||||||
|
// Sync txs from address info into payment_txs
|
||||||
|
if (info.in?.length) {
|
||||||
|
for (const tx of info.in) {
|
||||||
|
if (!tx.txid) continue;
|
||||||
|
await sql`
|
||||||
|
INSERT INTO payment_txs (payment_id, txid, amount, confirmed)
|
||||||
|
VALUES (${payment.id}, ${tx.txid}, ${String(tx.amount ?? 0)}, ${tx.block != null})
|
||||||
|
ON CONFLICT (payment_id, txid) DO UPDATE SET confirmed = EXCLUDED.confirmed
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (payment.status === "pending" || payment.status === "underpaid") {
|
if (payment.status === "pending" || payment.status === "underpaid") {
|
||||||
if (coin.confirmations === 0 && received >= threshold) {
|
if (coin.confirmations === 0 && received >= threshold) {
|
||||||
await sql`UPDATE payments SET amount_received = ${received.toFixed(8)}, txid = ${txid}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
await sql`UPDATE payments SET amount_received = ${received.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
||||||
await applyPlan(payment);
|
await applyPlan(payment);
|
||||||
} else if (coin.confirmations > 0 && receivedConfirmed >= threshold) {
|
} else if (coin.confirmations > 0 && receivedConfirmed >= threshold) {
|
||||||
await sql`UPDATE payments SET amount_received = ${receivedConfirmed.toFixed(8)}, txid = ${txid}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
// Check all txs confirmed
|
||||||
|
const [{ unconfirmed }] = await sql`
|
||||||
|
SELECT COUNT(*)::int as unconfirmed FROM payment_txs WHERE payment_id = ${payment.id} AND confirmed = false
|
||||||
|
`;
|
||||||
|
if (Number(unconfirmed) === 0) {
|
||||||
|
await sql`UPDATE payments SET amount_received = ${receivedConfirmed.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
||||||
await applyPlan(payment);
|
await applyPlan(payment);
|
||||||
|
}
|
||||||
} else if (received >= threshold) {
|
} else if (received >= threshold) {
|
||||||
await sql`UPDATE payments SET amount_received = ${received.toFixed(8)}, txid = ${txid}, status = 'confirming' WHERE id = ${payment.id}`;
|
await sql`UPDATE payments SET amount_received = ${received.toFixed(8)}, status = 'confirming' WHERE id = ${payment.id}`;
|
||||||
} else if (received > 0) {
|
} else if (received > 0) {
|
||||||
await sql`UPDATE payments SET amount_received = ${received.toFixed(8)}, txid = ${txid}, status = 'underpaid' WHERE id = ${payment.id}`;
|
await sql`UPDATE payments SET amount_received = ${received.toFixed(8)}, status = 'underpaid' WHERE id = ${payment.id}`;
|
||||||
}
|
}
|
||||||
} else if (payment.status === "confirming") {
|
} else if (payment.status === "confirming") {
|
||||||
if (receivedConfirmed >= threshold) {
|
const [{ unconfirmed }] = await sql`
|
||||||
|
SELECT COUNT(*)::int as unconfirmed FROM payment_txs WHERE payment_id = ${payment.id} AND confirmed = false
|
||||||
|
`;
|
||||||
|
if (receivedConfirmed >= threshold && Number(unconfirmed) === 0) {
|
||||||
await sql`UPDATE payments SET amount_received = ${receivedConfirmed.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
await sql`UPDATE payments SET amount_received = ${receivedConfirmed.toFixed(8)}, status = 'paid', paid_at = now() WHERE id = ${payment.id} AND status != 'paid'`;
|
||||||
await applyPlan(payment);
|
await applyPlan(payment);
|
||||||
}
|
}
|
||||||
|
|
@ -279,11 +302,6 @@ export function watchPayment(payment: any) {
|
||||||
addressMap.set(payment.address, payment);
|
addressMap.set(payment.address, payment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTxid(info: any): string | null {
|
|
||||||
if (info.in?.length) return info.in.map((i: any) => i.txid).filter(Boolean).join(",") || null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function applyPlan(payment: any) {
|
async function applyPlan(payment: any) {
|
||||||
if (payment.plan === "lifetime") {
|
if (payment.plan === "lifetime") {
|
||||||
await sql`UPDATE accounts SET plan = 'lifetime', plan_expires_at = NULL WHERE id = ${payment.account_id}`;
|
await sql`UPDATE accounts SET plan = 'lifetime', plan_expires_at = NULL WHERE id = ${payment.account_id}`;
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ export const routes = new Elysia()
|
||||||
address: payment.address,
|
address: payment.address,
|
||||||
status: payment.status,
|
status: payment.status,
|
||||||
expires_at: payment.expires_at,
|
expires_at: payment.expires_at,
|
||||||
|
txs: [],
|
||||||
qr_url: getQrUrl(uri),
|
qr_url: getQrUrl(uri),
|
||||||
coin_label: coinInfo.label,
|
coin_label: coinInfo.label,
|
||||||
coin_ticker: coinInfo.ticker,
|
coin_ticker: coinInfo.ticker,
|
||||||
|
|
@ -144,10 +145,16 @@ export const routes = new Elysia()
|
||||||
const amountReceived = parseFloat(payment.amount_received || "0");
|
const amountReceived = parseFloat(payment.amount_received || "0");
|
||||||
const amountRemaining = Math.max(0, amountCrypto - amountReceived);
|
const amountRemaining = Math.max(0, amountCrypto - amountReceived);
|
||||||
|
|
||||||
// QR shows remaining amount (or full amount if nothing received yet)
|
const qrAmount = amountRemaining > 0 && amountRemaining < amountCrypto
|
||||||
const qrAmount = amountRemaining > 0 ? amountRemaining.toFixed(8) : payment.amount_crypto;
|
? amountRemaining.toFixed(8) : payment.amount_crypto;
|
||||||
const uri = `${coinInfo.uri}:${payment.address}?amount=${qrAmount}`;
|
const uri = `${coinInfo.uri}:${payment.address}?amount=${qrAmount}`;
|
||||||
|
|
||||||
|
const txs = await sql`
|
||||||
|
SELECT txid, amount, confirmed, detected_at
|
||||||
|
FROM payment_txs WHERE payment_id = ${payment.id}
|
||||||
|
ORDER BY detected_at ASC
|
||||||
|
`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: payment.id,
|
id: payment.id,
|
||||||
plan: payment.plan,
|
plan: payment.plan,
|
||||||
|
|
@ -162,7 +169,7 @@ export const routes = new Elysia()
|
||||||
created_at: payment.created_at,
|
created_at: payment.created_at,
|
||||||
expires_at: payment.expires_at,
|
expires_at: payment.expires_at,
|
||||||
paid_at: payment.paid_at,
|
paid_at: payment.paid_at,
|
||||||
txid: payment.txid,
|
txs: txs.map((t: any) => ({ txid: t.txid, amount: t.amount, confirmed: t.confirmed, detected_at: t.detected_at })),
|
||||||
qr_url: getQrUrl(uri),
|
qr_url: getQrUrl(uri),
|
||||||
coin_label: coinInfo?.label,
|
coin_label: coinInfo?.label,
|
||||||
coin_ticker: coinInfo?.ticker,
|
coin_ticker: coinInfo?.ticker,
|
||||||
|
|
|
||||||
|
|
@ -281,27 +281,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncTxids(data) {
|
||||||
|
if (!data?.txs) return;
|
||||||
|
for (const tx of data.txs) {
|
||||||
|
if (tx.txid && !watchedTxids.includes(tx.txid)) watchedTxids.push(tx.txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyStatus(status, data) {
|
function applyStatus(status, data) {
|
||||||
if (status === 'underpaid') {
|
if (status === 'underpaid') {
|
||||||
document.getElementById('pay-status').innerHTML = `
|
document.getElementById('pay-status').innerHTML = `
|
||||||
<span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
|
<span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
|
||||||
<span class="text-yellow-400">Underpaid — please send the remaining amount</span>
|
<span class="text-yellow-400">Underpaid — please send the remaining amount</span>
|
||||||
`;
|
`;
|
||||||
if (data?.txid) {
|
syncTxids(data);
|
||||||
for (const t of data.txid.split(',')) {
|
|
||||||
if (!watchedTxids.includes(t)) watchedTxids.push(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (status === 'confirming') {
|
} else if (status === 'confirming') {
|
||||||
document.getElementById('pay-status').innerHTML = `
|
document.getElementById('pay-status').innerHTML = `
|
||||||
<span class="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
|
<span class="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
|
||||||
<span class="text-blue-400">Transaction detected, waiting for confirmation...</span>
|
<span class="text-blue-400">Transaction detected, waiting for confirmation...</span>
|
||||||
`;
|
`;
|
||||||
if (data?.txid) {
|
syncTxids(data);
|
||||||
for (const t of data.txid.split(',')) {
|
|
||||||
if (!watchedTxids.includes(t)) watchedTxids.push(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (status === 'paid') {
|
} else if (status === 'paid') {
|
||||||
clearInterval(pollInterval);
|
clearInterval(pollInterval);
|
||||||
clearInterval(countdownInterval);
|
clearInterval(countdownInterval);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue