Sponsor notifications: new referral + referral purchase
- notifyNewReferral (was nudgeReferrer): email a member's sponsor on EVERY new referral, activated or not. Resolve the sponsor from the join token by member id, share code, or username (was code-only and skipped already-activated sponsors). Reworded from an activation nudge to a real "you have a new referral" note; keeps the payouts reminder only for un-activated sponsors. - Purchase event now also emails the buyer's direct sponsor that their referral bought a package, noting whether it's a $20+ qualifying purchase. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -238,18 +238,29 @@ async function resolveSponsorToken(tok) {
|
|||||||
try { return await chain.memberIdByAccount(acct.address); } catch (e) { return 0; }
|
try { return await chain.memberIdByAccount(acct.address); } catch (e) { return 0; }
|
||||||
}
|
}
|
||||||
// The moment someone joins through a code, nudge its owner to activate.
|
// The moment someone joins through a code, nudge its owner to activate.
|
||||||
async function nudgeReferrer(ref) {
|
// Email a member's sponsor the moment they get a new referral (free OR paid).
|
||||||
|
// Resolves the sponsor from the join token by member id, share code, or username,
|
||||||
|
// and notifies EVERY sponsor — activated or not (an active sponsor still wants to
|
||||||
|
// know their team grew). A referral is on the line from signup; it only counts
|
||||||
|
// toward qualification once it makes a $20+ purchase.
|
||||||
|
async function notifyNewReferral(ref, newAcct) {
|
||||||
try {
|
try {
|
||||||
|
if (!mailer.hasKey()) return;
|
||||||
const t = String(ref || '').trim().toLowerCase();
|
const t = String(ref || '').trim().toLowerCase();
|
||||||
if (!t || /^\d+$/.test(t) || !mailer.hasKey()) return;
|
if (!t) return;
|
||||||
const owner = await accounts.byCode(t);
|
let owner = null;
|
||||||
if (!owner || !owner.email || owner.address) return; // already activated-ready
|
if (/^\d+$/.test(t)) { try { owner = await accounts.byMemberId(Number(t)); } catch (e) {} }
|
||||||
mailer.send(owner.email, 'Someone just joined through your InstantAdPay link',
|
if (!owner) { try { owner = await accounts.byCode(t); } catch (e) {} }
|
||||||
'Good news: a new member just signed up through your share link.\n\n'
|
if (!owner) { try { owner = await accounts.byUsername(t); } catch (e) {} }
|
||||||
+ 'One thing to do so you never miss a payment: sign in and switch on payouts '
|
if (!owner || !owner.email) return;
|
||||||
+ '(one free wallet step). The contract locks each buyer to their sponsor at their '
|
const who = newAcct && newAcct.username ? '@' + newAcct.username : 'A new member';
|
||||||
+ 'first purchase, so have payouts on before your people start buying.\n\n'
|
let body = who + ' just joined InstantAdPay through your link — they are on your team from today.\n\n'
|
||||||
+ 'https://instantadpay.com/my\n\nInstantAdPay').catch(e => console.error('nudge failed', e.message));
|
+ 'They count toward your qualification once they make a $20+ purchase.\n\n';
|
||||||
|
if (!owner.address) body += 'Make sure payouts are switched on (one free wallet step) so you never miss a commission — '
|
||||||
|
+ 'the contract locks each buyer to their sponsor at their first purchase.\n\n';
|
||||||
|
body += 'See your team: https://instantadpay.com/my\n\nInstantAdPay';
|
||||||
|
mailer.send(owner.email, 'You have a new referral on InstantAdPay', body)
|
||||||
|
.catch(e => console.error('referral notify failed', e.message));
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
// welcome email on a new account: onboarding steps + who their sponsor is
|
// welcome email on a new account: onboarding steps + who their sponsor is
|
||||||
@@ -288,6 +299,25 @@ async function emailOnEvent(ev) {
|
|||||||
'Your ad-credit balance is now: ' + bal + '\n' +
|
'Your ad-credit balance is now: ' + bal + '\n' +
|
||||||
'Amount paid: ' + weiToPol(ev.paidWei) + ' POL\n\n' +
|
'Amount paid: ' + weiToPol(ev.paidWei) + ' POL\n\n' +
|
||||||
'View your transaction on the blockchain:\n' + txUrl);
|
'View your transaction on the blockchain:\n' + txUrl);
|
||||||
|
// tell the buyer's DIRECT sponsor their referral just bought (upline earners
|
||||||
|
// are separately notified by the TierPaid payout email when they earn)
|
||||||
|
try {
|
||||||
|
const buyer = await chain.member(ev.buyerId);
|
||||||
|
if (buyer && buyer.sponsorId) {
|
||||||
|
const sp = await accounts.byMemberId(buyer.sponsorId);
|
||||||
|
if (sp && sp.email) {
|
||||||
|
const ba = await accounts.byMemberId(ev.buyerId);
|
||||||
|
const bn = ba && ba.username ? '@' + ba.username : 'One of your referrals';
|
||||||
|
const usd = ('$' + (ev.priceCents / 100).toFixed(2)).replace(/\.00$/, '');
|
||||||
|
const qual = ev.priceCents >= 2000
|
||||||
|
? ' This is a $20+ purchase, so it counts toward your qualification.'
|
||||||
|
: ' (Purchases under $20 do not count toward qualification.)';
|
||||||
|
mailer.send(sp.email, 'Your referral just bought an ad package',
|
||||||
|
bn + ' just purchased a package (' + usd + ' — ' + ev.creditAmount + ' credits).' + qual + '\n\n' +
|
||||||
|
'See your team and the live ledger: https://instantadpay.com/my\n\nInstantAdPay').catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
else if (ev.type === 'TierPaid') await notify(ev.recipientId, 'You just got paid on InstantAdPay', 'A level-' + ev.tier + ' payout of ' + weiToPol(ev.amountWei) + ' POL just landed in your wallet.');
|
else if (ev.type === 'TierPaid') await notify(ev.recipientId, 'You just got paid on InstantAdPay', 'A level-' + ev.tier + ' payout of ' + weiToPol(ev.amountWei) + ' POL just landed in your wallet.');
|
||||||
else if (ev.type === 'AwardPaid') await notify(ev.toId, 'You just got paid on InstantAdPay', weiToPol(ev.amountWei) + ' POL just landed in your wallet.');
|
else if (ev.type === 'AwardPaid') await notify(ev.toId, 'You just got paid on InstantAdPay', weiToPol(ev.amountWei) + ' POL just landed in your wallet.');
|
||||||
@@ -420,7 +450,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const ref = parseCookies(req)['iap.sponsor'] || ''; // first-touch attribution
|
const ref = parseCookies(req)['iap.sponsor'] || ''; // first-touch attribution
|
||||||
const r = await accounts.signup(b.email, b.password, ref);
|
const r = await accounts.signup(b.email, b.password, ref);
|
||||||
if (r.error) return json(res, 400, r);
|
if (r.error) return json(res, 400, r);
|
||||||
nudgeReferrer(ref).catch(() => {});
|
notifyNewReferral(ref, r.account).catch(() => {});
|
||||||
if (b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent
|
if (b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent
|
||||||
const token = await auth.mintSession({ email: r.account.email });
|
const token = await auth.mintSession({ email: r.account.email });
|
||||||
return json(res, 200, { ok: true, account: r.account }, { 'Set-Cookie': auth.sessionCookie(token) });
|
return json(res, 200, { ok: true, account: r.account }, { 'Set-Cookie': auth.sessionCookie(token) });
|
||||||
@@ -466,7 +496,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const ref = parseCookies(req)['iap.sponsor'] || '';
|
const ref = parseCookies(req)['iap.sponsor'] || '';
|
||||||
const r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged
|
const r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged
|
||||||
if (r.error) return json(res, 400, r);
|
if (r.error) return json(res, 400, r);
|
||||||
if (r.created) { nudgeReferrer(ref).catch(() => {}); sendWelcome(e, ref).catch(() => {}); }
|
if (r.created) { notifyNewReferral(ref, r.account).catch(() => {}); sendWelcome(e, ref).catch(() => {}); }
|
||||||
if (r.created && b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent, new joins only
|
if (r.created && b.newsletter) sendy.subscribe(r.account.email, r.account.username || '').catch(() => {}); // pre-checked opt-in, silent, new joins only
|
||||||
let memberId = 0;
|
let memberId = 0;
|
||||||
if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (err) {} }
|
if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (err) {} }
|
||||||
|
|||||||
Reference in New Issue
Block a user