@magic.batua/points
Version:
The Points module powers the loyalty points features of the Magic Batua platform.
184 lines (163 loc) • 6.99 kB
text/typescript
/**
* @module @magic.batua/points
* @overview Defines the `Ledger` class that manages loyalty points logic across the
* application.
*
* @author Animesh Mishra <hello@animesh.ltd>
* @copyright © 2018 Animesh Ltd. All Rights Reserved.
*/
import * as Crypto from "crypto"
import * as Request from "request-promise-native"
import * as Points from "./Source/Points"
import { ObjectID } from "mongodb"
import { ExternalError } from "@magic.batua/error"
import { ClientError } from "@magic.batua/error"
import { Code } from "@magic.batua/error"
import { Transaction, InitTransaction } from "./Source/Transaction"
/**
* The Points Ledger keeps track of a user's Magic Points, their status, their availability
* and other points related arithmetic.
*
* Magic Points are awarded to users as rewards for continued usage and word-of-mouth publicity.
* The points are rewarded as following:
*
* - 600 points at signup. These points aren't redeemable until you've spent ₹1,000 or more on the
* Magic Batua platform.
*
* - 400 points if somebody signs up using your referral code. These points become redeemable once
* you've amassed 10 or more referrals, and each of those referrals have spent ₹1,000 or more.
*
* - 1 point for mobile recharge. Redeemable immediately. If were referred by someone, their account
* is credited with a point too.
*
* - 20 points for tuition fee payments. Redeemable immediately. If were referred by someone, their
* account is credited with 20 points too.
*
* To manage the points logic, we make use of three buckets: Signup, Referrals and Available. As points
* earned through Signup and Referrals become redeemable they are emptied into the Available bucket.
* Points earned through other activities such as mobile recharge or tuition fee payment are credited to
* the Available bucket straightaway.
*/
export class Ledger {
/** Number of points available for redemption */
public available: number = 0
/**
* Points earned at signup. Becomes redeemable once the account has spent
* ₹1,000 or more on the Magic Batua platform.
*/
public signup: number = 0
/**
* Points earned through referrals. Becomes redeemable once the account has accrued
* 10 or more referrals.
*/
public referral: number = 0
/** Number of points accured since the creation of account */
public redeemable: number = 0
/** Points that have expired. */
public expired: number = 0
/** Points that have been redeemed since the creation of account */
public redeemed: number = 0
/** Points that have been refunded */
public refunded: number = 0
/** Points transactions listing all the issuance, redemption and refund transactions. */
public transactions = new Array<Transaction>()
public constructor(transactions: Array<InitTransaction>) {
for(var entry of transactions) {
this.transactions.push(new Transaction(entry))
}
// Count all points
this.count()
}
/**
* Goes over all point transactions one by one and separates all the points
* in their respective baskets.
*
* @param transactions An array of Points `Transaction`
*/
private count() {
this.signup = 0
this.referral = 0
this.expired = 0
this.redeemable = 0
this.refunded = 0
this.redeemed = 0
for(var transaction of this.transactions) {
switch(transaction.type) {
case "Issue":
if(transaction.expiryDate!.valueOf() <= Date.now()) { this.expired += transaction.points; break }
if(transaction.notes == "Signup") { this.signup += transaction.points; break }
if(transaction.notes == "Referral") { this.referral += transaction.points; break }
else { this.redeemable += transaction.points; break }
case "Redeem":
this.redeemed += transaction.points
break
case "Refund":
this.refunded += transaction.points
}
}
this.available = this.redeemable - this.redeemed
}
/** Issues a Magic Point for the given `reason` */
public Issue(points: number, reason: string): Transaction {
let transaction = new Transaction({
points: points,
type: "Issue",
notes: reason
})
// Add this transaction to the transactions array and count
// the points again
this.transactions.push(transaction)
this.count()
return transaction
}
/** Redeems `points` from the balance */
public Redeem(points: number, reason: string): Transaction {
if(points > this.available) {
throw new ClientError(Code.BadRequest,
"Trying to redeem " + points + " points, but the account has " + this.redeemable + " redeemable points only.")
}
let transaction = new Transaction({
points: points,
type: "Redeem",
notes: reason
})
// Add this transaction to the transactions array and count
// the points again
this.transactions.push(transaction)
this.count()
return transaction
}
/**
* This method will be called by the `Account` module when the condition for
* unlocking signup bonus is met.
* To move points from Signup bucket `this.signup` to Redeemable bucket `this.redeemable`,
* we change the `notes` property of signup points issuance from "Signup" to
* "Signup Points become active".
*/
public MakeSignupRedeemable() {
for(var transaction of this.transactions) {
if(transaction.notes == "Signup" && transaction.expiryDate!.valueOf() >= Date.now()) {
transaction.notes = "Signup points can be redeemed now."
}
}
this.count()
}
/**
* This method will be called by the `Account` module when the condition for
* unlocking signup bonus is met.
* To move points from Referrals bucket `this.signup` to Redeemable bucket `this.redeemable`,
* we change the `notes` property of signup points issuance from "Signup" to
* "Referral points can be redeemed now.".
*/
public MakeReferralsRedeemable() {
for(var transaction of this.transactions) {
if(transaction.notes == "Referral" && transaction.expiryDate!.valueOf() >= Date.now()) {
transaction.notes = "Referral points can now be redeemed."
}
}
this.count()
}
}
export { Transaction }
export { InitTransaction }
export { Points }