From 9df669fa7c0800fd4cf41c72b20ee1c87f601650 Mon Sep 17 00:00:00 2001 From: martbost Date: Mon, 21 Sep 2026 10:55:09 -0500 Subject: [PATCH] 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 --- public/assets/my.js | 4 ++-- public/my.html | 2 +- server.js | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/public/assets/my.js b/public/assets/my.js index 0571dd1..a286cc5 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -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 diff --git a/public/my.html b/public/my.html index 27ee3fb..b4a2d12 100644 --- a/public/my.html +++ b/public/my.html @@ -1070,7 +1070,7 @@ - + diff --git a/server.js b/server.js index 5a48c1a..2508453 100644 --- a/server.js +++ b/server.js @@ -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