Set up referral system!

This generates unique referral codes for people, and keeps track
(by email ID) of who referred whom.
This commit is contained in:
Badri 2022-02-20 15:50:06 +05:30
parent 8820f58eeb
commit d9785d601e
3 changed files with 200 additions and 13 deletions

View file

@ -188,7 +188,9 @@ router.use(bodyParser.urlencoded({
}))
// main views
router.get('/', async (req, res) => {
// the home view is used in a couple of places, so we've moved it out
let homeView = async (req, res) => {
if (DEBUG) console.debug('Returning home page')
// count people
@ -206,6 +208,25 @@ router.get('/', async (req, res) => {
).models
if (DEBUG) console.log(`Listing ${recentPledges.length} pledges`)
if (DEBUG && req.params.referralCode) console.log(`Referral code: ${req.params.referralCode}`)
let referrer
if (req.params.referralCode) {
try {
let pledge = await (Pledge
.query({
where: {
referral_code: req.params.referralCode,
}
})
.fetch())
referrer = pledge.get('anonymous') ? 'Anonymous' : pledge.get('name')
} catch(e) {
if (DEBUG) console.debug(`Skipping referral ${req.params.referralCode} because not found: ${e}`)
}
}
twing.render('index.htm.twig', {
'goal_rupees': Number(goalRupees).toLocaleString('en-IN'),
@ -215,13 +236,117 @@ router.get('/', async (req, res) => {
'percent_rupees': `style="width: ${total_rupees/goalRupees*100}%"`,
'percent_people': `style="width: ${total_people/goalPeople*100}%"`,
'recent_pledges': recentPledges,
'referral_code': req.params.referralCode || null, // if it's there
'referrer': referrer,
}).then((output) => {
res.end(output)
})
})
}
router.get('/', homeView)
router.get('/r/:referralCode', homeView)
// helpers to generate and decode referrals
function randomCode() {
let chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
let code = ''
for (let i=0; i<10; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
return code
}
async function findReferralCode(email) {
let referral_code
// fetch existing referral code, if exists
try {
let pledge = await (Pledge
.query({
where: {
email: email,
}
})
.fetch())
referral_code = pledge.get('referral_code')
if (DEBUG) console.log(`Using existing referral code: ${referral_code}`)
} catch(e) {
if (e.message == 'EmptyResponse' && DEBUG) {
console.log('No matching referrals found')
}
}
if (!referral_code) {
// no code found; create a new one and ensure uniqueness
console.log(`Generating new referral code for: ${email}`)
async function checkUniqueness(referral_code) {
let a = await UnverifiedPledge.query({where: {referral_code: referral_code}}).count()
let b = await Pledge.query({where: {referral_code: referral_code}}).count()
return a == 0 && b == 0
}
do {
referral_code = randomCode()
} while (!checkUniqueness(referral_code))
}
return referral_code
}
async function findReferrer(code) {
let email
// first, check for an entry with this referral code
try {
let pledge = await (Pledge
.query({
where: {
referral_code: code,
}
})
.fetch())
email = pledge.get('email')
console.log(`Found: ${email}`)
} catch(e) {
if (e.message == 'EmptyResponse' && DEBUG) {
console.log('No matching referrals found')
} else {
console.error(e)
}
// next, check for an entry with this email
// (maybe it's already been converted from code to email)
try {
let pledge = await (Pledge
.query({
where: {
email: code,
}
})
.fetch())
email = pledge.get('email')
console.log('it seems to be')
} catch(e) {
if (e.message == 'EmptyResponse' && DEBUG) {
console.debug('No matching emails found either')
} else {
console.error(e)
}
return null
}
}
return email
}
// function to validate pledges before saving
function validatePledge(body, PledgeModel = Pledge) {
async function validatePledge(body, PledgeModel = Pledge) {
// errors get saved here
let errors = []
@ -268,6 +393,19 @@ function validatePledge(body, PledgeModel = Pledge) {
let overseas = body.overseas == 'yes' ? true : false
let other_message = body.other_message
// manage referrals
let referral_code = await findReferralCode(email)
let referrer = null
console.log('ref co', body.referral_code)
if (body.referral_code) {
try {
referrer = await findReferrer(body.referral_code)
} catch (e) {
console.debug(`Error loading referrer: ${e}`)
}
}
// enter the info
let pledge = new PledgeModel() // may be Pledge or UnverifiedPledge
pledge.set('was_robot', was_robot)
@ -281,6 +419,9 @@ function validatePledge(body, PledgeModel = Pledge) {
pledge.set('get_newsletter', get_newsletter)
pledge.set('other_message', other_message)
pledge.set('referral_code', referral_code)
pledge.set('referrer', referrer)
// return it all!
return {
pledge: pledge,
@ -298,7 +439,7 @@ router.post('/pledge', async (req, res) => {
}
// process the pledge with our handy function
let {pledge, errors} = validatePledge(req.body, UnverifiedPledge)
let {pledge, errors} = await validatePledge(req.body, UnverifiedPledge)
// fail if there were errors
if (!!errors.length) {
@ -454,6 +595,32 @@ router.get('/verify', async (req, res) => {
newPledge.set('get_newsletter', pledge.get('get_newsletter'))
newPledge.set('other_message', pledge.get('other_message'))
newPledge.set('referral_code', await findReferralCode(pledge.get('email')))
// set referrer in priority: old pledge > new pledge
try {
let oldPledge = await (Pledge
.query({
where: {
email: req.query.email,
}
})
.fetch())
newPledge.set('referrer',
oldPledge.get('referrer') || pledge.get('referrer') || null)
} catch(e) {
if (e.message == 'EmptyResponse' && DEBUG) {
console.log('No matching referrals found')
} else {
console.error(e)
}
newPledge.set('referrer', pledge.get('referrer') || null)
}
console.log(`Saved referrer: ${newPledge.get('referrer')}`)
// destroy previous pledges by that user
try {
await (Pledge
@ -471,6 +638,7 @@ router.get('/verify', async (req, res) => {
await newPledge.save()
// send the confirmation email
let referralLink = `${req.protocol}://${req.hostname}/?ref=${newPledge.get('referral_code')}`
let text = `Hi ${pledge.get('name')},
Your pledge of ${Number(pledge.get('amount')).toLocaleString('en-IN')} to Snipette Analog has been saved!
@ -494,6 +662,16 @@ a pledge with the same email address as you used before.
If you have any questions, don't hesitate to reach out: you can drop a line
anytime to editors@snipettemag.com.
### Share for Stickers!
Money helps, but we need people tooand that's where you can help! Here's your
unique referral link:
${referralLink}
Get 5 or more of your friends to sign up using that link, and, once we collect
the money and start printing, we'll send you a free pack of Snipette stickers!
Thanks,
The Snipette Team`
@ -505,11 +683,13 @@ The Snipette Team`
})
// Send confirmation message
res.redirect('/thanks')
res.redirect(`/thanks?ref=${newPledge.get('referral_code')}`)
})
router.get('/thanks', async (req, res) => {
let output = await twing.render('thanks.htm.twig')
let output = await twing.render('thanks.htm.twig', {
referral_code: req.query.ref,
})
res.send(output)
return

View file

@ -33,7 +33,7 @@
</head>
<body class="bg-orange-200 font-serif">
<div class="bg-orange-600 uppercase text-lg text-orange-800 p-5 font-sans">
Snipette Analog Fundraiser
Snipette Analog Fundraiser{% if referrer %} (referred by: {{referrer}}){% endif %}
</div>
<div class="bg-orange-600 text-center p-10">
<img alt="Snipette" class="text-5xl uppercase font-sans w-full max-w-3xl mx-auto" src="/img/header.png"/>
@ -385,6 +385,7 @@
<input name="was_robot" value="no" id="input-robo-no" type="radio" class="text-xl font-sans"/>
<label for="input-robo-no" class="text-xl font-sans">No, of course not</label>
</div>
{% if referral_code %}<input type="hidden" name="referral_code" value="{{referral_code}}"/>{% endif %}
</div>
</div>

View file

@ -1,17 +1,23 @@
{% extends "base.htm.twig" %}
{% block title %}Snipette Crowdfunding: Error{% endblock %}
{% block heading %}Thank you!{% endblock %}
{% block heading %}{% if referral_code %}Wait, there's stickers!{% else %}Thank you!{% endif %}{% endblock %}
{% block message %}
<p class="text-lg mb-2">Your pledge has been recorded. Thank you so much! Please check your inbox for a receipt.</p>
<p class="text-md mb-8">Meanwhile, to help us reach our subscriber goal, do consider sharing our campaign with your friends and family. We can't print if we don't have enough subscribers to send copies too, so who knows: this could be even more helpful than the pledge itself!</p>
<p class="text-md mb-8">
{% if referral_code %}
Meanwhile to help us reach our goal, we're offering free stickers! All you have to do is share your unique referral link with friends and family. If 5 or more of them pledge, you'll get a set of Snipette-themed puppy stickers along with your first issue! Find your referral link below:
{% else %}
Meanwhile, to help us reach our subscriber goal, do consider sharing our campaign with your friends and family. We can't print if we don't have enough subscribers to send copies too, so who knows: this could be even more helpful than the pledge itself!
{% endif %}
</p>
<p class="flex space-x-1 items-center justify-center mb-8">
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-size="large" data-text="Just added my #micropledge to help Snipette&#39;s print version! Help them reach their subscriber goals to bring down the price for everyone 💗" data-url="https://fund.snipettemag.com/" data-show-count="false">Tweet</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<iframe src="https://www.facebook.com/plugins/share_button.php?href=https%3A%2F%2Ffund.snipettemag.com&layout=button&size=large&appId=878224506053413&width=77&height=28" width="77" height="28" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowfullscreen="true" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>
<a class="bg-green-500 text-white no-underline text-sm bold p-2 rounded-lg" href="whatsapp://send?text=Just added my #micropledge to help Snipette&#39;s print version! Help them reach their subscriber goals to bring down the price for everyone 💗" data-action="share/whatsapp/share" target="_blank">WhatsApp</a>
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-size="large" data-text="Just added my #micropledge to help Snipette&#39;s print version! Help them reach their subscriber goals to bring down the price for everyone 💗" data-url="https://fund.snipettemag.com{% if referral_code %}/r/{{referral_code}}{% endif %}" data-show-count="false">Tweet</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<iframe src="https://www.facebook.com/plugins/share_button.php?href=https%3A%2F%2Ffund.snipettemag.com{% if referral_code %}%2Fr%2F{{referral_code}}{% endif %}&layout=button&size=large&appId=878224506053413&width=77&height=28" width="77" height="28" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowfullscreen="true" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"></iframe>
<a class="bg-green-500 text-white no-underline text-sm bold p-2 rounded-lg" href="whatsapp://send?text=Just added my #micropledge to help Snipette&#39;s print version! Help them reach their subscriber goals to bring down the price for everyone, at https://fund.snipettemag.com{% if referral_code %}/r/{{referral_code}}{% endif %} 💗" data-action="share/whatsapp/share" target="_blank">WhatsApp</a>
</p>
<p class="mb-8 flex justify-center">
<input type="text" disabled id="share_link" name="share_link" value="https://fund.snipettemag.com" class="p-2 inline-block h-auto grow sm:grow-0"/>
<input type="text" disabled id="share_link" name="share_link" value="https://fund.snipettemag.com{% if referral_code %}/r/{{referral_code}}{% endif %}" class="p-2 inline-block h-auto grow sm:grow-0"/>
<script type="text/javascript">
function copyShareURL() {
let shareLink = document.getElementById('share_link')