Sponsor attribution: last touch (30-day cookie), locked at account creation; 'Joining under' line at the code step; chatbot answer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -489,9 +489,12 @@ const server = http.createServer(async (req, res) => {
|
||||
const u = new URL(req.url, 'http://x');
|
||||
const p = u.pathname;
|
||||
|
||||
// -- join links: /join/<memberId or share code> — first-touch cookie.
|
||||
// Codes resolve LATE (at buy time) to whatever chain id the referrer
|
||||
// has by then, so free members refer from day one.
|
||||
// -- join links: /join/<memberId or share code> — LAST-touch cookie (Marty,
|
||||
// 2026-09-10): the link a visitor opened most recently is the sponsor shown
|
||||
// and used, and it locks the moment the account is created (accounts.ensure
|
||||
// never changes an existing account's sponsor; the contract binds the buyer at
|
||||
// their first purchase). Codes resolve LATE (at buy time) to whatever chain id
|
||||
// the referrer has by then, so free members refer from day one.
|
||||
let m = /^\/join\/([A-Za-z0-9_]{1,20})$/.exec(p);
|
||||
if (m && (req.method === 'GET' || req.method === 'HEAD')) {
|
||||
// lead-capture page: email first, wallet later. ?v=<angle> picks the hook
|
||||
@@ -501,9 +504,9 @@ const server = http.createServer(async (req, res) => {
|
||||
const angle = String(u.searchParams.get('v') || '').toLowerCase();
|
||||
const ang = JOIN_ANGLES[angle] || null;
|
||||
if (req.method === 'GET') coach.recordView(tok, ang ? angle : '', req.headers.referer); // link stats per angle + source
|
||||
const cookieTail = `; Path=/; SameSite=Lax; Max-Age=${180 * 24 * 3600}${IS_PROD ? '; Secure' : ''}`;
|
||||
const cookieTail = `; Path=/; SameSite=Lax; Max-Age=${30 * 24 * 3600}${IS_PROD ? '; Secure' : ''}`; // 30 days: whoever brings them back gets the credit
|
||||
const set = [];
|
||||
if (!cookies['iap.sponsor']) set.push('iap.sponsor=' + tok + cookieTail);
|
||||
set.push('iap.sponsor=' + tok + cookieTail); // last touch wins
|
||||
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);
|
||||
@@ -628,7 +631,7 @@ const server = http.createServer(async (req, res) => {
|
||||
// out only at purchase / payout-activation time and gets linked then)
|
||||
if (p === '/api/signup' && req.method === 'POST') {
|
||||
const b = await readBody(req);
|
||||
const ref = parseCookies(req)['iap.sponsor'] || ''; // first-touch attribution
|
||||
const ref = parseCookies(req)['iap.sponsor'] || ''; // last-touch attribution, locked at account creation
|
||||
const r = await accounts.signup(b.email, b.password, ref);
|
||||
if (r.error) return json(res, 400, r);
|
||||
// sponsor is notified once the new member picks a username (onboarding),
|
||||
|
||||
Reference in New Issue
Block a user