Payment trace: self-serve "who was paid, who was skipped and why" per purchase (Earnings tab + admin member card)
Marty, 2026-09-16, after the third "why didn't my sponsor get paid" thread of the day (livedreams / gracie25,
Morten / Terry's linked wallets). GET /api/my/trace?who=<#|username> (yourself, anyone up to 3 levels
below you, or your own 3 uplines) and GET /api/admin/trace?who= list every purchase the member was
part of, newest first: buyer, sponsor, package, then levels 1-3 with the recipient, the skipped
positions and the reason ("not qualified: had N of 2 qualifying buyers then"), the platform share, and
a verify link. Linked extra wallets are named after their owner. Earnings tab card "Trace a payment";
admin member card gets a "Payment trace" section; chatbot canned answer + AI prompt route the
question there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -777,6 +777,45 @@
|
||||
fill('refFeed', a.referrals, 'No referral activity yet. Share your invite link.');
|
||||
fill('buyFeed', a.purchases, 'No purchases from your wallet yet.');
|
||||
} catch (e) {}
|
||||
initTrace();
|
||||
}
|
||||
// payment trace (2026-09-16): who was paid, who was skipped and why, per purchase
|
||||
let traceInit = false;
|
||||
function initTrace() {
|
||||
if (traceInit || !$('trGo')) return; traceInit = true;
|
||||
const run = async () => {
|
||||
const who = ($('trWho').value || '').trim(); const out = $('trOut'); const msg = $('trMsg');
|
||||
msg.textContent = 'Reading the chain…'; out.innerHTML = '';
|
||||
try {
|
||||
const c = await IAP.getConfig();
|
||||
const r = await (await fetch('/api/my/trace?who=' + encodeURIComponent(who))).json();
|
||||
if (r.error) { msg.textContent = r.error; return; }
|
||||
msg.textContent = '';
|
||||
const nm = id => id ? ('#' + id + (r.names[id] ? ' @' + esc(r.names[id]) : '')) : 'nobody';
|
||||
const pol = w => IAP.fmtPol(w) + ' POL';
|
||||
const when = ts => new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
||||
let h = '<p class="small" style="margin:0 0 8px"><b>' + nm(r.memberId) + '</b> · ' + (r.activated ? 'activated under ' + nm(r.sponsorId) : 'not activated on the chain yet') + ' · ' + r.buyersNow + ' qualifying buyer' + (r.buyersNow === 1 ? '' : 's') + ' (level 2 needs 2, level 3 needs 5)</p>';
|
||||
if (!r.purchases.length) { out.innerHTML = h + '<p class="muted small">No purchases involving ' + nm(r.memberId) + ' yet.</p>'; return; }
|
||||
for (const p of r.purchases) {
|
||||
const isBuyer = p.buyerId === r.memberId;
|
||||
h += '<div class="card" style="padding:10px 12px;margin:0 0 8px">'
|
||||
+ '<div class="small"><b>' + when(p.ts) + '</b> · ' + nm(p.buyerId) + ' bought a $' + (p.priceCents / 100).toFixed(0) + ' package (' + pol(p.paidWei) + ')' + (p.sponsorId ? ' · sponsor ' + nm(p.sponsorId) : '')
|
||||
+ (c.explorer ? ' · <a target="_blank" rel="noopener" href="' + c.explorer + '/tx/' + p.tx + '">verify ↗</a>' : '') + '</div>';
|
||||
for (const t of p.tiers) {
|
||||
const me = t.paidTo === r.memberId, skippedMe = t.skipped.some(x => x.id === r.memberId);
|
||||
let line = '<b>Level ' + t.tier + ' (' + t.pct + '%)</b>: ';
|
||||
for (const x of t.skipped) line += '<span style="color:#f0a742">' + nm(x.id) + ' skipped (' + (x.reason === 'unqualified' ? 'not qualified: had ' + x.buyersThen + ' of ' + (t.tier === 2 ? 2 : 5) + ' qualifying buyers then' : esc(x.reason)) + ')</span> → ';
|
||||
line += t.paidTo ? '<span style="color:' + (me ? '#3ecf7a' : 'inherit') + '">' + nm(t.paidTo) + ' paid ' + pol(t.amountWei) + '</span>' : '<span class="muted">no eligible catcher within reach → company</span>';
|
||||
h += '<div class="small" style="margin-top:4px' + (me || skippedMe ? ';background:rgba(255,255,255,.04);border-radius:6px;padding:2px 6px' : '') + '">' + line + '</div>';
|
||||
}
|
||||
h += '<div class="small muted" style="margin-top:4px">Platform (20%): ' + pol(p.adminWei) + (isBuyer ? '' : '') + '</div></div>';
|
||||
}
|
||||
h += '<p class="muted small" style="margin:6px 0 0">A skipped level means that person had not unlocked it when the purchase settled. Qualification never applies backwards; the next purchase after they qualify pays them.</p>';
|
||||
out.innerHTML = h;
|
||||
} catch (e) { msg.textContent = 'Could not read the chain just now. Try again in a minute.'; }
|
||||
};
|
||||
$('trGo').addEventListener('click', run);
|
||||
$('trWho').addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); run(); } });
|
||||
}
|
||||
|
||||
// credit activity ledger under the campaign table (2026-09-16): every earn and spend with its reason
|
||||
|
||||
Reference in New Issue
Block a user