} = this.pluginLibraries;
const lodash = require('lodash');
const sequelize = require('sequelize');
const umzug = require('umzug');
const cron = require('node-cron');
const { body, validationResult } = require('express-validator');
const AVAILABLE_REQUIREMENTS = {
title: 'KYC Verification',
description: 'Require users to verify their identity'
title: 'Email Verification',
description: 'Require users to verify their email'
title: 'SMS Verification',
description: 'Require users to input a valid phone number'
title: 'Bank Verification',
description: 'Require users to input a valid bank account and status is 3'
const init = async () => {
const umzugInstance = new umzug({
sequelize: toolsLib.database.getModel('sequelize'),
modelName: 'PluginMigrations',
tableName: 'PluginMigrations'
migrations: umzug.migrationsList(
name: 'automatic_tier_upgrade-add_requirements_column',
up: (queryInterface, Sequelize) => queryInterface.describeTable('Tiers')
if (table['requirements']) {
return new Promise((resolve) => resolve());
return queryInterface.addColumn('Tiers', 'requirements', {
down: (queryInterface, Sequelize) => queryInterface.describeTable('Tiers')
if (table['requirements']) {
return queryInterface.removeColumn('Tiers', 'requirements');
[toolsLib.database.getModel('sequelize').getQueryInterface(), sequelize]
const pending = await umzugInstance.pending();
if (pending.length > 0) {
await umzugInstance.up('automatic_tier_upgrade-add_requirements_column');
const findAllTiers = async () => {
return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Tiers" ORDER BY id ASC', {
type: sequelize.QueryTypes.SELECT
const findTier = async (id) => {
return toolsLib.database.getModel('sequelize').query('SELECT * FROM "Tiers" WHERE id = :id', {
type: sequelize.QueryTypes.SELECT
const updateTier = async (id, requirements) => {
const tier = await toolsLib.database.getModel('sequelize').query('UPDATE "Tiers" SET requirements = :requirements WHERE id = :id RETURNING *', {
requirements: JSON.stringify(requirements)
const validateTierUpdate = async (tier_id, data = []) => {
const tiers = await findAllTiers();
// if removing all requirements, check if upper requirements exist
if (lodash.isEmpty(data)) {
const isInvalid = tiers.some((tier) => {
return tier.id > tier_id && !lodash.isEmpty(tier.requirements);
throw new Error('Cannot remove requirements if a higher tier has requirements set');
// check if lower tier has requirements set if level is 3 or above
const isInvalid = tiers.some((tier) => {
return tier.id >= 2 && tier.id < tier_id && lodash.isEmpty(tier.requirements);
throw new Error('Lower tiers must have requirements set');
// check if given static requirements are already set for other tier levels
const isInvalid = tiers.some((tier) => {
const existingRequirements = lodash.intersection(data, tier.requirements);
return tier.id !== tier_id && !lodash.isEmpty(existingRequirements);
throw new Error('Static requirements can only be set for one tier');
const runner = async () => {
const tiers = await findAllTiers();
const users = await toolsLib.database.findAll('user', {
const groupedUsers = lodash.groupBy(users, 'verification_level');
for (const level in groupedUsers) {
const userLevel = parseInt(level);
for (const user of groupedUsers[level]) {
'AUTO TIER UPGRADE PLUGIN',
`Checking verifications for user ${user.email} with level ${userLevel}`
let updatedLevel = userLevel;
const checkedTiers = tiers.filter((tier) => tier.id >= 2 && tier.id > userLevel);
const userVerifications = [];
if (user.id_data.status === 3) {
userVerifications.push('kyc_verification');
if (!lodash.isEmpty(user.phone_number)) {
userVerifications.push('sms_verification');
if (user.email_verified) {
userVerifications.push('email_verification');
if(!lodash.isEmpty(user.bank_account) && user.bank_account.some((account) => account.status === 3)){
userVerifications.push('bank_verification');
'AUTO TIER UPGRADE PLUGIN',
for (const tier of checkedTiers) {
if (lodash.isEmpty(tier.requirements)) {
'AUTO TIER UPGRADE PLUGIN',
`Tier ${tier.id} does not have any requirement set`
if (lodash.difference(tier.requirements, userVerifications).length === 0) {
'AUTO TIER UPGRADE PLUGIN',
`User ${user.email} meets requirements for tier`,
if (updatedLevel > userLevel) {
'AUTO TIER UPGRADE PLUGIN',
`User ${user.email} level will be changed from ${userLevel} to ${updatedLevel}`
await toolsLib.user.changeUserVerificationLevelById(user.id, updatedLevel);
const cronjob = cron.schedule('0 0 0 * * *', async () => {
'/plugins/automatic-tier-upgrade Upgrade start'
'/plugins/automatic-tier-upgrade error during upgrade:',
'/plugins/automatic-tier-upgrade/available-requirements',
[toolsLib.security.verifyBearerTokenExpressMiddleware(['admin'])],
'/plugins/automatic-tier-upgrade/available-requirements',
return res.json(AVAILABLE_REQUIREMENTS);
'/plugins/automatic-tier-upgrade/requirements',
[toolsLib.security.verifyBearerTokenExpressMiddleware(['admin'])],
'GET /plugins/automatic-tier-upgrade/requirements auth',
const tiers = await findAllTiers();
for (const tier of tiers) {
response[tier.id] = tier.requirements;
return res.json(response);
'GET /plugins/automatic-tier-upgrade/requirements err',
return res.status(err.status || 400).json({message: err.message});
'/plugins/automatic-tier-upgrade/requirements',
toolsLib.security.verifyBearerTokenExpressMiddleware(['admin']),
body('tier').isInt({ min: 1 }),
body('requirements').isArray()
const errors = validationResult(req);
return res.status(400).json({errors: errors.array()});
'PUT /plugins/automatic-tier-upgrade/requirements auth',
const { tier: level, requirements } = req.body;
const tier = await findTier(level);
throw new Error(`Tier ${level} does not exist`);
const formattedRequirements = lodash.uniq(requirements);
if (lodash.difference(formattedRequirements, Object.keys(AVAILABLE_REQUIREMENTS)).length > 0) {
throw new Error('Invalid requirements given');
await validateTierUpdate(level, requirements);
const updatedTier = await updateTier(tier.id, requirements);
lodash.pick(updatedTier, ['id', 'requirements'])
'PUT /plugins/automatic-tier-upgrade/requirements err',
return res.status(err.status || 400).json({message: err.message});
'AUTOMATIC TIER UPGRADE PLUGIN error during initialization:',