Missed-payout notice shows the whole chain, one message per purchase

Marty (2026-09-19): show every missed qualification so the whole chain is visible. The old
notice fired once per passed-up tier and showed only that tier. AdminPaid is the last event of
every purchase transaction (119 of 119 in the index), so the notice now fires there, gathers
every PassedUp and TierPaid of the tx, and missed.js composes one message: each tier's walk,
every person passed over with their buyer count, who finally caught it (or that the company
kept it), and the total POL that walked past how many people on that one sale.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 13:14:12 -05:00
parent d90f08d54a
commit c00ec64cf8
2 changed files with 65 additions and 13 deletions
+12 -13
View File
@@ -28,7 +28,8 @@ const coach = require('./coach'); // coaching view, nudges, digest, prospects,
const tank = require('./tank'); // holding tank: unsponsored free members, adoptions, pay-it-forward
const legacy = require('./legacy'); // Faucet Wave / Tier One Ads bridge: welcome-back credits for listed emails
const traffic = require('./traffic'); // public page views by referring domain (admin Traffic tab)
const promos = require('./promos'); // partner promo codes -> free ad credits (link ?promo=CODE or the dashboard box)
const promos = require('./promos');
const missedNotice = require('./missed'); // the whole-chain missed-payout notice // partner promo codes -> free ad credits (link ?promo=CODE or the dashboard box)
const blog = require('./blog');
const adminMember = require('./adminmember');
const syndicate = require('./syndicate');
@@ -952,19 +953,17 @@ async function telegramOnEvent(ev) {
let missed = null;
try {
const site = 'https://instantadpay.com/my';
if (ev.type === 'TierPaid' && Number(ev.hops) > 0) {
// (a) on-chain pass-ups: AdminPaid is the last event of every purchase tx, so by then every
// PassedUp and TierPaid of the tx is indexed; one message shows the whole chain (missed.js).
if (ev.type === 'AdminPaid') {
const all = chain.recentEvents(1e9);
const sk = all.filter(x => x.type === 'PassedUp' && x.tx === ev.tx && x.tier === ev.tier);
if (sk.length) {
const nm2 = await accounts.namesForMembers(sk.map(x => x.skippedId)).catch(() => ({}));
const need = ev.tier === 2 ? 2 : 5;
const parts = sk.map(x => {
const had = all.filter(y => y.type === 'BuyerCounted' && y.sponsorId === x.skippedId && y.block < ev.block).length;
const why = x.reason === 'unqualified' ? 'not qualified for level ' + ev.tier + ' yet: ' + had + ' of ' + need + ' qualifying buyers' : x.reason === 'send-failed' ? 'the wallet refused the payment' : String(x.reason || '');
return '#' + x.skippedId + (nm2[x.skippedId] ? ' @' + nm2[x.skippedId] : '') + ' (' + why + ')';
});
missed = '\u{1F62C} <b>Missed payout</b>: ' + parts.join(' and ') + ' passed over on ' + who(ev.buyerId) + "'s purchase. <b>" + weiToPol(ev.amountWei) + ' POL</b> went to ' + who(ev.recipientId) + ' instead.'
+ '\nQualify before the next one: <a href="' + site + '">' + site.replace(/^https:\/\//, '') + '</a>';
const txEvents = all.filter(x => x.tx === ev.tx);
if (txEvents.some(x => x.type === 'PassedUp')) {
const ids = new Set(); txEvents.forEach(x => { for (const k of ['buyerId', 'skippedId', 'recipientId']) if (x[k]) ids.add(x[k]); });
const nm2 = await accounts.namesForMembers([...ids]).catch(() => ({}));
const who2 = id => '#' + id + (nm2[id] ? ' @' + nm2[id] : '');
const had = (id, tier) => all.filter(y => y.type === 'BuyerCounted' && y.sponsorId === id && y.block < ev.block).length;
missed = missedNotice.composeMissed(txEvents, who2, had, site);
}
}
if (ev.type === 'Purchase' && movedByTx.has(ev.tx)) {