forked from bitcoinafterlife/bal-welist-website
Show deposit progress after the invoice
- The invoice checks the server every 30 seconds and shows the amount detected, then the confirmation once the server is listed. - The server page gets the same banner, with a 24-hour limit on checks. - The invoice link is now "Follow your payment", so the invoice can be closed and the deposit followed from a fixed address.
This commit is contained in:
57
index.html
57
index.html
@@ -639,7 +639,61 @@ window.addEventListener('popstate', () => {
|
||||
fetchMinAmount();
|
||||
});
|
||||
|
||||
// After an invoice is shown, ask the server page every 30 seconds whether the
|
||||
// payment has been seen, so the visitor does not have to reload. The status only
|
||||
// moves forward: unconfirmed_balance may go back to 0 before the deposit is
|
||||
// credited, and that must not look as if the payment had disappeared.
|
||||
const DEPOSIT_POLL_MS = 30000;
|
||||
const DEPOSIT_POLL_MAX_MS = 24 * 60 * 60 * 1000;
|
||||
let depositWatch = null;
|
||||
|
||||
function stopDepositWatch() {
|
||||
if (depositWatch) clearInterval(depositWatch.timer);
|
||||
depositWatch = null;
|
||||
}
|
||||
|
||||
async function pollDeposit() {
|
||||
const watch = depositWatch;
|
||||
if (!watch) return;
|
||||
if (Date.now() - watch.started > DEPOSIT_POLL_MAX_MS) return stopDepositWatch();
|
||||
let data;
|
||||
try {
|
||||
const response = await fetch(`/weinfodata/${watch.chain}/${watch.id}`);
|
||||
if (!response.ok) return;
|
||||
data = await response.json();
|
||||
} catch (err) {
|
||||
return; // temporary network problem: try again at the next round
|
||||
}
|
||||
if (watch !== depositWatch || !data) return;
|
||||
const expiry = data.expiry_date || null;
|
||||
// The first answer is the reference: listing or renewal changes the expiry date.
|
||||
if (watch.expiry === undefined) watch.expiry = expiry;
|
||||
const status = document.getElementById('iv-status');
|
||||
const bal = data.balance != null ? fmt(data.balance) : null;
|
||||
const unc = data.unconfirmed_balance != null ? fmt(data.unconfirmed_balance) : null;
|
||||
if (bal) document.getElementById('iv-balance').textContent = unc ? bal + ' (' + unc + ')' : bal;
|
||||
if (expiry && expiry !== watch.expiry) {
|
||||
status.className = 'ok-line';
|
||||
status.textContent = '✓ Payment confirmed: your server is now listed.';
|
||||
stopDepositWatch();
|
||||
fetchData();
|
||||
} else if (Number(data.unconfirmed_balance) > 0 && !watch.detected) {
|
||||
watch.detected = true;
|
||||
status.className = 'ok-line';
|
||||
status.textContent = 'Payment detected: ' + fmt(data.unconfirmed_balance) + ' sats, waiting for 3 confirmations (about 30 minutes). This page updates by itself, or use the "Follow your payment" link below.';
|
||||
}
|
||||
}
|
||||
|
||||
function startDepositWatch(chain, id) {
|
||||
stopDepositWatch();
|
||||
if (!id) return;
|
||||
depositWatch = { chain, id, started: Date.now(), detected: false, expiry: undefined };
|
||||
depositWatch.timer = setInterval(pollDeposit, DEPOSIT_POLL_MS);
|
||||
pollDeposit();
|
||||
}
|
||||
|
||||
document.getElementById('submit').addEventListener('click', async () => {
|
||||
stopDepositWatch();
|
||||
const urlInput = document.getElementById('url');
|
||||
let url = urlInput.value.trim();
|
||||
if (!url) return;
|
||||
@@ -739,6 +793,8 @@ document.getElementById('terms-accept').addEventListener('click', async function
|
||||
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
return;
|
||||
}
|
||||
// Fixed address: whoever pays can close this invoice and follow the deposit there.
|
||||
if (weLink) weinfoEl.textContent = 'Follow your payment';
|
||||
const amountSats = result.min_amount != null ? result.min_amount : minAmount;
|
||||
const amountBtc = (amountSats / 1e8).toFixed(8).replace(/\.?0+$/, '');
|
||||
const invoiceuri = `bitcoin:${result.address}?amount=${amountBtc}&label=BAL&message=Order+For+url+%26+${encodeURIComponent(url)}`;
|
||||
@@ -773,6 +829,7 @@ document.getElementById('terms-accept').addEventListener('click', async function
|
||||
const inv = document.getElementById('invoice');
|
||||
inv.classList.add('show');
|
||||
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
startDepositWatch(result.chain || chain, result.id);
|
||||
} catch (err) {
|
||||
document.getElementById('iv-status').className = 'err-line';
|
||||
document.getElementById('iv-status').textContent = 'Network error. Please try again.';
|
||||
|
||||
64
weinfo.html
64
weinfo.html
@@ -36,6 +36,7 @@
|
||||
<h2 id="wename"> </h2>
|
||||
<h3 id="weinfo"> </h3>
|
||||
<h4 id="wechain"> </h4>
|
||||
<div id="wepayment" style="display:none;max-width:640px;margin:10px auto 18px;padding:10px 14px;border-radius:8px;font-weight:600"></div>
|
||||
</div>
|
||||
<div style="float:left;border:1;margin-right:50px;">
|
||||
<div><span>Version:</span><span id="weversion"> </span></div>
|
||||
@@ -77,6 +78,69 @@
|
||||
</div>
|
||||
<a href="" id="download">Download</a>
|
||||
<script type="text/javascript">
|
||||
// Payment banner: this page has a fixed address, so whoever paid can close the
|
||||
// invoice and follow the deposit here. It asks the server data every 30 seconds
|
||||
// until the server is listed. The status only moves forward, because
|
||||
// unconfirmed_balance may go back to 0 before the deposit is credited.
|
||||
const PAYMENT_POLL_MS = 30000;
|
||||
const PAYMENT_POLL_MAX_MS = 24 * 60 * 60 * 1000;
|
||||
const paymentStarted = Date.now();
|
||||
let paymentTimer = null, paymentSeen = false, paymentAmount = 0, firstExpiry;
|
||||
|
||||
function showPayment(text, ok) {
|
||||
const box = document.getElementById('wepayment');
|
||||
box.textContent = text;
|
||||
box.style.display = 'block';
|
||||
box.style.color = ok ? '#14703a' : '#7a5200';
|
||||
box.style.background = ok ? '#e6f6ec' : '#fdf3dc';
|
||||
}
|
||||
|
||||
function stopPayment() {
|
||||
if (paymentTimer) clearInterval(paymentTimer);
|
||||
paymentTimer = null;
|
||||
}
|
||||
|
||||
async function pollPayment() {
|
||||
if (Date.now() - paymentStarted > PAYMENT_POLL_MAX_MS) return stopPayment();
|
||||
const path = window.location.pathname.split('/').filter(p => p !== '');
|
||||
let data;
|
||||
try {
|
||||
const response = await fetch('/weinfodata/' + path[1] + '/' + path[2]);
|
||||
if (!response.ok) return;
|
||||
data = await response.json();
|
||||
} catch (err) {
|
||||
return; // temporary network problem: try again at the next round
|
||||
}
|
||||
if (!data) return;
|
||||
const expiry = data.expiry_date || null;
|
||||
if (firstExpiry === undefined) firstExpiry = expiry;
|
||||
const unconfirmed = Number(data.unconfirmed_balance) > 0;
|
||||
const active = expiry && new Date(expiry.replace(' ', 'T') + 'Z') > new Date();
|
||||
const bal = data.balance != null ? data.balance : null;
|
||||
const unc = data.unconfirmed_balance != null ? data.unconfirmed_balance : null;
|
||||
document.getElementById('webalance').textContent = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—';
|
||||
const expiryCell = document.createElement('b');
|
||||
expiryCell.textContent = expiry || '—';
|
||||
document.getElementById('weexpiry').replaceChildren(expiryCell);
|
||||
if (expiry && expiry !== firstExpiry) {
|
||||
showPayment('✓ Payment confirmed: this server is now listed.', true);
|
||||
stopPayment();
|
||||
} else if (unconfirmed || paymentSeen) {
|
||||
paymentSeen = true;
|
||||
if (unconfirmed) paymentAmount = Number(data.unconfirmed_balance);
|
||||
showPayment('Payment detected: ' + paymentAmount.toLocaleString('en-US') + ' sats, waiting for 3 confirmations (about 30 minutes). This page updates by itself.', false);
|
||||
} else if (active) {
|
||||
stopPayment(); // already listed and nothing incoming: no banner, nothing to follow
|
||||
} else {
|
||||
showPayment('Waiting for payment. This page updates by itself.', false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
paymentTimer = setInterval(pollPayment, PAYMENT_POLL_MS);
|
||||
pollPayment();
|
||||
});
|
||||
|
||||
function getWeInfo(){
|
||||
const path = window.location.pathname.split('/').filter(p => p !== '');
|
||||
const we_id = path[2];
|
||||
|
||||
Reference in New Issue
Block a user