Partner-code credits arrive as a drip; unclaimed installments expire
Marty (2026-09-19): partner codes keep working with no monthly cap, but the credits stop being a dump. A fifth is paid the day the code is redeemed, then a fifth on each day the member finishes their daily ad set, five days in all, and whatever is unclaimed 30 days after redemption is never paid. Same headline number for the partner to promote; paid only to people who show up. 49 of the 52 PARTNER redemptions were still sitting on 500+ unspent. Every redemption now records the site the ?promo= link was opened from (iap.promoref cookie set from the Referer at link-open), so a code's traffic can be attributed to the partner page it was supposed to come from. Member-funded codes charge the funder one installment at a time; a short funder leaves the installment due, not lost. Pre-drip redemptions were paid in full up front and are marked paid=credits at start-up, so nothing is paid twice. Covered by a module test (13 checks: per-day cadence, gaps, rounding, expiry, pre-drip rows, declined payer) and an end-to-end HTTP test on a local copy (9 checks). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,7 @@ function dropAdminSession(req) { const t = adminTokenOf(req); if (t && adminSess
|
||||
function adminCookie(t) { return 'iap.adm=' + encodeURIComponent(t) + '; Path=/; HttpOnly; SameSite=Lax; Max-Age=' + (ADMIN_TTL / 1000) + (IS_PROD ? '; Secure' : ''); }
|
||||
function clearAdminCookie() { return 'iap.adm=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0'; }
|
||||
const IS_PROD = process.env.NODE_ENV === 'production';
|
||||
const todayCT = () => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Chicago' }); // Marty's day, never UTC
|
||||
const SITE_FILE = path.join(DATA_DIR, 'site.json');
|
||||
|
||||
const db = require('./db');
|
||||
@@ -701,6 +702,20 @@ async function sponsorGainNudge(sp, buyerName, lostNames) {
|
||||
await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [sp.email], subject, html, 'notice');
|
||||
} catch (e) {}
|
||||
}
|
||||
// Partner-code credits arrive as a drip (Marty, 2026-09-19): a fifth on the day the code is
|
||||
// redeemed, then a fifth on each day the member finishes their daily ad set, five days in all,
|
||||
// nothing after 30 days. Same headline number for the partner to promote; paid only to people
|
||||
// who show up. Member-funded codes charge the funder one installment at a time.
|
||||
async function payPromo(email, day) {
|
||||
return promos.payDue(email, day, async (r, amount, n) => {
|
||||
if (r.funder && r.funder !== email) {
|
||||
const ok = await ads.spendEarned(r.funder, amount, { log: { kind: 'promo', note: 'Partner code ' + r.code + ' funded for ' + email + ' (day ' + n + ' of ' + promos.STEPS + ')' } });
|
||||
if (!ok) { console.log('promo funder short', r.code, r.funder); return false; }
|
||||
}
|
||||
await ads.addEarned(email, amount, { log: { kind: 'promo', note: 'Partner code ' + r.code + ': ' + amount + ' of ' + r.credits + ' (day ' + n + ' of ' + promos.STEPS + ')' } });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
// anti-fraud admin alert (Telegram admin chat, else email): who, which flags, and whether the sign-up was blocked
|
||||
function fraudAlert(email, fc, spAcct, blocked) {
|
||||
try {
|
||||
@@ -1053,6 +1068,7 @@ const server = http.createServer(async (req, res) => {
|
||||
const set = [];
|
||||
set.push('iap.sponsor=' + tok + cookieTail); // last touch wins
|
||||
const promo = promos.norm(u.searchParams.get('promo')); if (promo) set.push('iap.promo=' + promo + cookieTail); // partner code, redeemed at signup
|
||||
if (promo) { const pref = String(req.headers.referer || '').replace(/^https?:\/\//, '').split('/')[0].toLowerCase().slice(0, 80); if (pref) set.push('iap.promoref=' + encodeURIComponent(pref) + cookieTail); } // which partner page it came from
|
||||
if (ang) set.push('iap.angle=' + angle + cookieTail);
|
||||
if (!cookies['iap.ref']) set.push('iap.ref=' + encodeURIComponent(coach.refHost(req.headers.referer)) + cookieTail); // first-touch source
|
||||
return serveJoinPage(res, tok, ang ? angle : '', ang, set);
|
||||
@@ -1305,7 +1321,11 @@ const server = http.createServer(async (req, res) => {
|
||||
// partner promo code carried on the join link: redeem once per account (ignored if invalid/used)
|
||||
try {
|
||||
const pc = parseCookies(req)['iap.promo'];
|
||||
if (pc) { const g = await promos.redeem(pc, e, 'link'); if (g.ok) { if (g.funder && g.funder !== e) { if (await ads.spendEarned(g.funder, g.credits, { log: { kind: 'promo', note: 'Partner code ' + g.code + ' funded for ' + e } })) { await ads.addEarned(e, g.credits, { log: { kind: 'promo', note: 'Partner code ' + g.code } }); console.log('promo redeemed (member-funded)', g.code, g.credits, e, 'by', g.funder); } else console.log('promo funder short', g.code, g.funder); } else { await ads.addEarned(e, g.credits, { log: { kind: 'promo', note: 'Partner code ' + g.code } }); console.log('promo redeemed', g.code, g.credits, e); } } }
|
||||
if (pc) {
|
||||
const pref = parseCookies(req)['iap.promoref'] ? decodeURIComponent(parseCookies(req)['iap.promoref']) : '';
|
||||
const g = await promos.redeem(pc, e, 'link', pref);
|
||||
if (g.ok) { const paid = await payPromo(e, todayCT()); console.log('promo redeemed', g.code, 'day 1:', paid.map(x => x.amount).join('+') || 0, 'of', g.credits, e, pref ? 'from ' + pref : ''); }
|
||||
}
|
||||
} catch (err) { console.error('promo redeem', err.message); }
|
||||
// legacy bridge: a listed former Faucet Wave / Tier One Ads member gets welcome-back credits once
|
||||
if (r.created && /^(fw|t1)-(adv|earn)$/.test(via)) {
|
||||
@@ -1561,10 +1581,10 @@ const server = http.createServer(async (req, res) => {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const b = await readBody(req);
|
||||
const g = await promos.redeem(b.code, s.email, 'dashboard');
|
||||
const g = await promos.redeem(b.code, s.email, 'dashboard', '');
|
||||
if (g.error) return json(res, 400, g);
|
||||
await ads.addEarned(s.email, g.credits, { log: { kind: 'promo', note: 'Partner code ' + String(b.code || '').toUpperCase() } });
|
||||
return json(res, 200, { ok: true, credits: g.credits, code: g.code, partner: g.partner });
|
||||
const paid = await payPromo(s.email, todayCT());
|
||||
return json(res, 200, { ok: true, credits: g.credits, now: paid.reduce((n, x) => n + x.amount, 0), step: g.step, steps: g.steps, code: g.code, partner: g.partner });
|
||||
}
|
||||
// -- admin: member card. GET ?q= resolves email / @username / #id / share code / wallet;
|
||||
// PATCH edits username, sponsor, main wallet or grants credits; DELETE removes a free account (2026-09-13)
|
||||
@@ -2085,6 +2105,7 @@ const server = http.createServer(async (req, res) => {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const r = await ads.claimDaily(s.email);
|
||||
if (!r.error) { try { const paid = await payPromo(s.email, todayCT()); if (paid.length) r.promo = paid; } catch (e) { console.error('promo drip', e.message); } }
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
// -- downline lineage: 3 levels, usernames+IDs; email only for directs
|
||||
|
||||
Reference in New Issue
Block a user