Sponsor pre-check alert: only the checkout call may alert (the dashboard's display-only call never does), and the alert says what it is: a heads-up before the sale, nothing paid yet

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-21 10:55:09 -05:00
parent a403ce3520
commit 9df669fa7c
3 changed files with 9 additions and 8 deletions
+2 -2
View File
@@ -14,7 +14,7 @@
// if they arrived through a sponsor's link, show who they're joining under
(async () => {
try {
const sp = await (await fetch('/api/sponsor')).json();
const sp = await (await fetch('/api/sponsor?intent=view')).json(); // display only: never an alert
if (sp && sp.invited && sp.name && $('sponsorNote')) { $('sponsorNoteName').textContent = sp.name; $('sponsorNote').hidden = false; }
} catch (e) {}
})();
@@ -2217,7 +2217,7 @@
}
if (cur !== wantAddr) throw new Error('Your wallet connected as ' + cur.slice(0, 6) + '…' + cur.slice(-4) + ' but you chose ' + wantAddr.slice(0, 6) + '…' + wantAddr.slice(-4) + '. Switch accounts in your wallet app and try again.');
}
const spNow = fromPos ? { sponsorId: meNow.memberId } : await jretry('/api/sponsor');
const spNow = fromPos ? { sponsorId: meNow.memberId } : await jretry('/api/sponsor?intent=buy');
if (!fromPos && !meNow.memberId && spNow.sponsorBlocked) { IAP.status(sponsorHoldText(spNow), 'bad'); return; } // first activation must not hand a sponsored member to the company
if (!fromPos && !meNow.memberId && spNow.sponsorRouted) IAP.status((spNow.sponsorRouted.from || 'Your sponsor') + ' has not switched on payouts, so this purchase credits ' + spNow.sponsorRouted.to + ' instead. You are not held up; carrying on.', 'ok');
// pre-flight: stop early if the POL is not there. Trust Wallet also hard-blocks any
+1 -1
View File
@@ -1070,7 +1070,7 @@
<script src="/assets/common.js?v=20260916a"></script>
<script src="/assets/wallet.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260921d"></script>
<script src="/assets/my.js?v=20260924c"></script>
<script src="/assets/my.js?v=20260924d"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>
</html>
+6 -5
View File
@@ -760,11 +760,12 @@ function fraudAlert(email, fc, spAcct, blocked) {
const sponsorRoutedLast = new Map(); // buyer email -> ts (one alert per buyer per hour)
function sponsorRoutedAlert(who, spd, routed, skipped) {
const k = String(who || '?'); if (Date.now() - (sponsorRoutedLast.get(k) || 0) < 3600000) return; sponsorRoutedLast.set(k, Date.now());
const text = '\u2934\uFE0F InstantAdPay: purchase by ' + k.replace(/^(.{2}).*(@.*)$/, '$1***$2') + ' routed past ' + (spd.name || '?')
+ ' (payouts not switched on) to ' + routed.to + '. The buyer was not held up.' + (skipped.length > 1 ? ' Skipped: ' + skipped.map(x => x.name).join(', ') + '.' : '');
const text = '\u2934\uFE0F InstantAdPay heads-up: ' + k.replace(/^(.{2}).*(@.*)$/, '$1***$2') + ' is at checkout. Their sponsor ' + (spd.name || '?')
+ ' has not switched on payouts, so if this purchase goes through it pays ' + routed.to + ' instead.' + (skipped.length > 1 ? ' Also skipped: ' + skipped.map(x => x.name).join(', ') + '.' : '')
+ ' Nothing has been paid yet; the on-chain result posts to Telegram when the sale settles.';
const sc = siteConfig();
if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {});
else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay: purchase routed past an unactivated sponsor', text).catch(() => {});
else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay heads-up: a checkout will route past an unactivated sponsor', text).catch(() => {});
// the skipped sponsor and the one who gains are told by sponsorSyncOnEvent, once the position exists on the chain
}
const sponsorAlertLast = new Map(); // email -> ts (one alert per member per hour)
@@ -1267,9 +1268,9 @@ const server = http.createServer(async (req, res) => {
const catchId = Number(siteConfig().defaultSponsorId) || 1;
sponsorId = w.id;
sponsorRouted = { from: spd.name || null, to: w.id ? w.name : 'the company (#' + catchId + ')', toId: w.id || catchId, skipped: w.skipped.map(x => x.name) };
sponsorRoutedAlert(acct.email, spd, sponsorRouted, w.skipped);
if (u.searchParams.get('intent') === 'buy') sponsorRoutedAlert(acct.email, spd, sponsorRouted, w.skipped); // the dashboard's display-only pre-check never alerts (Marty, 2026-09-21)
}
} else if (acct && acct.sponsorRef && !sponsorId && spd.reason !== 'none') { sponsorBlocked = spd.reason; sponsorBlockedAlert(acct.email, Object.assign({ tok }, spd)); }
} else if (acct && acct.sponsorRef && !sponsorId && spd.reason !== 'none') { sponsorBlocked = spd.reason; if (u.searchParams.get('intent') === 'buy') sponsorBlockedAlert(acct.email, Object.assign({ tok }, spd)); }
if (sponsorId && await payoutChainBlocked(sponsorId, 2)) { console.log('sponsor routed away from no-payout chain', sponsorId); sponsorId = 0; }
// orphan fallback: an unresolvable/absent sponsor (dead link, no link) lands
// the new member under the configured catch position (#1) instead of root