diff --git a/server.js b/server.js index 7ea5f61..e82b1ba 100644 --- a/server.js +++ b/server.js @@ -238,18 +238,29 @@ async function resolveSponsorToken(tok) { try { return await chain.memberIdByAccount(acct.address); } catch (e) { return 0; } } // 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 { + if (!mailer.hasKey()) return; const t = String(ref || '').trim().toLowerCase(); - if (!t || /^\d+$/.test(t) || !mailer.hasKey()) return; - const owner = await accounts.byCode(t); - if (!owner || !owner.email || owner.address) return; // already activated-ready - mailer.send(owner.email, 'Someone just joined through your InstantAdPay link', - 'Good news: a new member just signed up through your share link.\n\n' - + 'One thing to do so you never miss a payment: sign in and switch on payouts ' - + '(one free wallet step). The contract locks each buyer to their sponsor at their ' - + 'first purchase, so have payouts on before your people start buying.\n\n' - + 'https://instantadpay.com/my\n\nInstantAdPay').catch(e => console.error('nudge failed', e.message)); + if (!t) return; + let owner = null; + if (/^\d+$/.test(t)) { try { owner = await accounts.byMemberId(Number(t)); } catch (e) {} } + if (!owner) { try { owner = await accounts.byCode(t); } catch (e) {} } + if (!owner) { try { owner = await accounts.byUsername(t); } catch (e) {} } + if (!owner || !owner.email) return; + const who = newAcct && newAcct.username ? '@' + newAcct.username : 'A new member'; + let body = who + ' just joined InstantAdPay through your link — they are on your team from today.\n\n' + + '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) {} } // 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' + 'Amount paid: ' + weiToPol(ev.paidWei) + ' POL\n\n' + '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 === '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 r = await accounts.signup(b.email, b.password, ref); 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 const token = await auth.mintSession({ email: r.account.email }); 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 r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged 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 let memberId = 0; if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (err) {} }