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:
martbost
2026-09-19 06:24:14 -05:00
parent 5ebe890760
commit 415a5f3848
5 changed files with 69 additions and 13 deletions
+5
View File
@@ -156,6 +156,11 @@ async function bootstrap() {
code VARCHAR(24) NOT NULL, email VARCHAR(190) NOT NULL, credits INT NOT NULL, via VARCHAR(12) NOT NULL, ts BIGINT NOT NULL, code VARCHAR(24) NOT NULL, email VARCHAR(190) NOT NULL, credits INT NOT NULL, via VARCHAR(12) NOT NULL, ts BIGINT NOT NULL,
PRIMARY KEY (code, email) PRIMARY KEY (code, email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await alterSafe0('ALTER TABLE promo_redemptions ADD COLUMN paid INT NOT NULL DEFAULT 0'); // credits paid so far (the drip)
await alterSafe0('ALTER TABLE promo_redemptions ADD COLUMN last_day CHAR(10) NULL'); // Central day of the last installment
await alterSafe0('ALTER TABLE promo_redemptions ADD COLUMN expires BIGINT NOT NULL DEFAULT 0'); // unclaimed installments stop here; 0 = pre-drip row
await alterSafe0('ALTER TABLE promo_redemptions ADD COLUMN ref VARCHAR(80) NULL'); // site the ?promo= link was opened from
await q('UPDATE promo_redemptions SET paid=credits WHERE expires=0 AND paid<credits'); // pre-drip redemptions were paid in full at once
await q(`CREATE TABLE IF NOT EXISTS page_hits ( await q(`CREATE TABLE IF NOT EXISTS page_hits (
day CHAR(10) NOT NULL, host VARCHAR(80) NOT NULL, path VARCHAR(40) NOT NULL, n INT NOT NULL DEFAULT 0, day CHAR(10) NOT NULL, host VARCHAR(80) NOT NULL, path VARCHAR(40) NOT NULL, n INT NOT NULL DEFAULT 0,
PRIMARY KEY (day, host, path) PRIMARY KEY (day, host, path)
+35 -7
View File
@@ -9,6 +9,12 @@ const path = require('path');
const db = require('./db'); const db = require('./db');
let DATA_DIR = null; let DATA_DIR = null;
const norm = c => String(c || '').trim().toUpperCase().replace(/[^A-Z0-9_-]/g, '').slice(0, 24); const norm = c => String(c || '').trim().toUpperCase().replace(/[^A-Z0-9_-]/g, '').slice(0, 24);
// The drip (Marty, 2026-09-19): a code's credits arrive in STEPS installments, the first on the
// day it is redeemed and one more on each day the member finishes their daily ad set. Whatever
// is still unclaimed WINDOW_DAYS after redemption is never paid.
const STEPS = 5;
const WINDOW_DAYS = 30;
const stepOf = credits => Math.ceil(Number(credits) / STEPS);
const J = { const J = {
db: { v: 1, codes: {}, redemptions: [] }, db: { v: 1, codes: {}, redemptions: [] },
@@ -21,7 +27,9 @@ const J = {
async uses(code) { return this.db.redemptions.filter(r => r.code === code).length; }, async uses(code) { return this.db.redemptions.filter(r => r.code === code).length; },
async redeemed(code, email) { return this.db.redemptions.some(r => r.code === code && r.email === email); }, async redeemed(code, email) { return this.db.redemptions.some(r => r.code === code && r.email === email); },
async addRedemption(r) { this.db.redemptions.push(r); this.save(); }, async addRedemption(r) { this.db.redemptions.push(r); this.save(); },
async redemptions(code, n) { return this.db.redemptions.filter(r => !code || r.code === code).slice(-(n || 200)).reverse(); } async redemptions(code, n) { return this.db.redemptions.filter(r => !code || r.code === code).slice(-(n || 200)).reverse(); },
async forEmail(email) { return this.db.redemptions.filter(r => r.email === email); },
async markPaid(code, email, paid, lastDay) { const r = this.db.redemptions.find(x => x.code === code && x.email === email); if (r) { r.paid = paid; r.lastDay = lastDay; this.save(); } }
}; };
const D = { const D = {
async get(code) { const r = await db.q('SELECT * FROM promo_codes WHERE code=?', [code]); return r[0] ? row(r[0]) : null; }, async get(code) { const r = await db.q('SELECT * FROM promo_codes WHERE code=?', [code]); return r[0] ? row(r[0]) : null; },
@@ -33,7 +41,9 @@ const D = {
}, },
async uses(code) { const r = await db.q('SELECT COUNT(*) n FROM promo_redemptions WHERE code=?', [code]); return Number(r[0].n); }, async uses(code) { const r = await db.q('SELECT COUNT(*) n FROM promo_redemptions WHERE code=?', [code]); return Number(r[0].n); },
async redeemed(code, email) { const r = await db.q('SELECT 1 FROM promo_redemptions WHERE code=? AND email=?', [code, email]); return r.length > 0; }, async redeemed(code, email) { const r = await db.q('SELECT 1 FROM promo_redemptions WHERE code=? AND email=?', [code, email]); return r.length > 0; },
async addRedemption(r) { await db.q('INSERT IGNORE INTO promo_redemptions (code,email,credits,via,ts) VALUES (?,?,?,?,?)', [r.code, r.email, r.credits, r.via, r.ts]); }, async addRedemption(r) { await db.q('INSERT IGNORE INTO promo_redemptions (code,email,credits,via,ts,paid,last_day,expires,ref) VALUES (?,?,?,?,?,?,?,?,?)', [r.code, r.email, r.credits, r.via, r.ts, r.paid || 0, r.lastDay || '', r.expires || 0, r.ref || null]); },
async forEmail(email) { return (await db.q('SELECT * FROM promo_redemptions WHERE email=?', [email])).map(x => ({ code: x.code, email: x.email, credits: Number(x.credits), via: x.via, ts: Number(x.ts), paid: Number(x.paid) || 0, lastDay: x.last_day || '', expires: Number(x.expires) || 0, ref: x.ref || '' })); },
async markPaid(code, email, paid, lastDay) { await db.q('UPDATE promo_redemptions SET paid=?, last_day=? WHERE code=? AND email=?', [paid, lastDay, code, email]); },
async redemptions(code, n) { const rows = code ? await db.q('SELECT * FROM promo_redemptions WHERE code=? ORDER BY ts DESC LIMIT ?', [code, Number(n) || 200]) : await db.q('SELECT * FROM promo_redemptions ORDER BY ts DESC LIMIT ?', [Number(n) || 200]); return rows.map(r => ({ code: r.code, email: r.email, credits: Number(r.credits), via: r.via, ts: Number(r.ts) })); } async redemptions(code, n) { const rows = code ? await db.q('SELECT * FROM promo_redemptions WHERE code=? ORDER BY ts DESC LIMIT ?', [code, Number(n) || 200]) : await db.q('SELECT * FROM promo_redemptions ORDER BY ts DESC LIMIT ?', [Number(n) || 200]); return rows.map(r => ({ code: r.code, email: r.email, credits: Number(r.credits), via: r.via, ts: Number(r.ts) })); }
}; };
const row = r => ({ code: r.code, funder: r.funder || null, credits: Number(r.credits), partner: r.partner || '', note: r.note || '', maxUses: Number(r.max_uses) || 0, expires: Number(r.expires) || 0, active: !!Number(r.active), created: Number(r.created) }); const row = r => ({ code: r.code, funder: r.funder || null, credits: Number(r.credits), partner: r.partner || '', note: r.note || '', maxUses: Number(r.max_uses) || 0, expires: Number(r.expires) || 0, active: !!Number(r.active), created: Number(r.created) });
@@ -57,13 +67,31 @@ async function check(code, email) {
if (c.maxUses && (await impl().uses(k)) >= c.maxUses) return { error: 'That promo code has been fully redeemed.' }; if (c.maxUses && (await impl().uses(k)) >= c.maxUses) return { error: 'That promo code has been fully redeemed.' };
return null; return null;
} }
// redeem for an account; the caller adds the credits. via = 'link' | 'dashboard' // redeem for an account: records the redemption; the caller then pays what is due today with
async function redeem(code, email, via) { // payDue(). via = 'link' | 'dashboard'; ref = the site the ?promo= link was opened from.
async function redeem(code, email, via, ref) {
const k = norm(code); const e = String(email || '').toLowerCase(); const k = norm(code); const e = String(email || '').toLowerCase();
const bad = await check(k, e); if (bad) return bad; const bad = await check(k, e); if (bad) return bad;
const c = await impl().get(k); const c = await impl().get(k);
await impl().addRedemption({ code: k, email: e, credits: c.credits, via: via || 'dashboard', ts: Date.now() }); const ts = Date.now();
return { ok: true, credits: c.credits, code: k, partner: c.partner, funder: c.funder || null }; await impl().addRedemption({ code: k, email: e, credits: c.credits, via: via || 'dashboard', ts, paid: 0, lastDay: '', expires: ts + WINDOW_DAYS * 86400000, ref: String(ref || '').slice(0, 80) });
return { ok: true, credits: c.credits, step: stepOf(c.credits), steps: STEPS, code: k, partner: c.partner, funder: c.funder || null };
}
// Pay every installment that is due today for this member. pay(rec, amount, n) moves the credits
// (and charges a funder) and returns true when it did; a false leaves the installment for another
// day. day is the member's calendar day (Central), so one installment per day, never two.
async function payDue(email, day, pay) {
const e = String(email || '').toLowerCase(); const out = [];
for (const r of await impl().forEmail(e)) {
if (!r.expires || r.paid >= r.credits || r.lastDay === day || Date.now() > r.expires) continue;
const c = await impl().get(r.code); if (!c) continue;
const amount = Math.min(stepOf(r.credits), r.credits - r.paid);
const n = Math.floor(r.paid / stepOf(r.credits)) + 1;
if (!(await pay(Object.assign({}, r, { funder: c.funder || null, partner: c.partner }), amount, n))) continue;
await impl().markPaid(r.code, e, r.paid + amount, day);
out.push({ code: r.code, amount, n, of: STEPS, total: r.credits, partner: c.partner });
}
return out;
} }
async function adminView() { async function adminView() {
const codes = await impl().list(); const codes = await impl().list();
@@ -71,4 +99,4 @@ async function adminView() {
return { codes, recent: await impl().redemptions(null, 100) }; return { codes, recent: await impl().redemptions(null, 100) };
} }
async function byFunder(email) { return (await impl().list()).filter(c => c.funder === String(email || '').toLowerCase()); } async function byFunder(email) { return (await impl().list()).filter(c => c.funder === String(email || '').toLowerCase()); }
module.exports = { init, create, setActive, check, redeem, adminView, norm, byFunder }; module.exports = { init, create, setActive, check, redeem, payDue, adminView, norm, byFunder, STEPS, WINDOW_DAYS };
+3 -1
View File
@@ -2822,7 +2822,9 @@
try { try {
const r = await (await fetch('/api/my/promo/redeem', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) })).json(); const r = await (await fetch('/api/my/promo/redeem', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) })).json();
if (r.error) { say(r.error, false); return; } if (r.error) { say(r.error, false); return; }
say('Added ' + Number(r.credits).toLocaleString() + ' credits' + (r.partner ? ' from ' + r.partner : '') + '. They are in your balance now.', true); say(r.step
? Number(r.now || 0).toLocaleString() + ' credits added now' + (r.partner ? ' from ' + r.partner : '') + '. The rest of the ' + Number(r.credits).toLocaleString() + ' arrive ' + r.step + ' at a time, one for each day you finish your daily ads, for the next 30 days.'
: 'Added ' + Number(r.credits).toLocaleString() + ' credits' + (r.partner ? ' from ' + r.partner : '') + '. They are in your balance now.', true);
inp.value = ''; if (typeof loadDashboard === 'function') loadDashboard(); inp.value = ''; if (typeof loadDashboard === 'function') loadDashboard();
} catch (e) { say('Could not apply that code. Try again.', false); } } catch (e) { say('Could not apply that code. Try again.', false); }
finally { btn.disabled = false; } finally { btn.disabled = false; }
+1 -1
View File
@@ -1037,7 +1037,7 @@
<script src="/assets/common.js?v=20260916a"></script> <script src="/assets/common.js?v=20260916a"></script>
<script src="/assets/wallet.js?v=20260911a"></script> <script src="/assets/wallet.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260911a"></script> <script src="/assets/promo.js?v=20260911a"></script>
<script src="/assets/my.js?v=20260919a"></script> <script src="/assets/my.js?v=20260919b"></script>
<script src="/assets/chat.js?v=20260907l"></script> <script src="/assets/chat.js?v=20260907l"></script>
</body> </body>
</html> </html>
+25 -4
View File
@@ -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 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'; } function clearAdminCookie() { return 'iap.adm=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0'; }
const IS_PROD = process.env.NODE_ENV === 'production'; 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 SITE_FILE = path.join(DATA_DIR, 'site.json');
const db = require('./db'); 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'); await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [sp.email], subject, html, 'notice');
} catch (e) {} } 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 // 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) { function fraudAlert(email, fc, spAcct, blocked) {
try { try {
@@ -1053,6 +1068,7 @@ const server = http.createServer(async (req, res) => {
const set = []; const set = [];
set.push('iap.sponsor=' + tok + cookieTail); // last touch wins 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 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 (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 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); 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) // partner promo code carried on the join link: redeem once per account (ignored if invalid/used)
try { try {
const pc = parseCookies(req)['iap.promo']; 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); } } catch (err) { console.error('promo redeem', err.message); }
// legacy bridge: a listed former Faucet Wave / Tier One Ads member gets welcome-back credits once // 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)) { 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); const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const b = await readBody(req); 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); 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() } }); const paid = await payPromo(s.email, todayCT());
return json(res, 200, { ok: true, credits: g.credits, code: g.code, partner: g.partner }); 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; // -- 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) // 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); const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const r = await ads.claimDaily(s.email); 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); return json(res, r.error ? 400 : 200, r);
} }
// -- downline lineage: 3 levels, usernames+IDs; email only for directs // -- downline lineage: 3 levels, usernames+IDs; email only for directs