diff --git a/banktransfer.js b/banktransfer.js new file mode 100644 index 0000000..eb271ee --- /dev/null +++ b/banktransfer.js @@ -0,0 +1,85 @@ +const models = require('./models'); +const { + exists, zrevrange, zadd, expire, zincrby, +} = require('./redis'); + +function processResult(amount) { + if (amount === 10) { + return { + success: false, + error: 'Bank transfer failed', + }; + } + return { + success: true, + message: 'Transfer was successful', + }; +} + +const BANK_TRANSFER_PROCESSORS_SET_NAME = 'banktf_processors_list'; + +// function that calls paystack endpoint +function paystackBT(amount) { + return processResult(amount); +} + +// function that calls flutterwave endpoint +function flutterwaveBT(amount) { + return processResult(amount); +} + +// function that calls monnify endpoint +function monnifyBT(amount) { + return processResult(amount); +} + +// general function that calls processor function based on processor +function processBankTransfer(processor, amount) { + if (processor === 'paystack') { + return paystackBT(amount); + } + if (processor === 'flutterwave') { + return flutterwaveBT(amount); + } + if (processor === 'monnify') { + return monnifyBT(amount); + } +} + +// main function +async function transferToBank(amount) { + const processors = await models.bank_transfer_processors.find( + { where: { enabled: true }, raw: true }, + ); + const setArray = []; + const setExists = await exists(BANK_TRANSFER_PROCESSORS_SET_NAME); + + if (!setExists) { + processors.forEach((processor) => { + let score = 0; + if (processor.position === 1) { + score = 30; + } + if (processor.position === 2) { + score = 10; + } + setArray.push(score); + setArray.push(processor.name); + }); + await zadd(BANK_TRANSFER_PROCESSORS_SET_NAME, setArray); + await expire(BANK_TRANSFER_PROCESSORS_SET_NAME, 1800); // set should refresh in 30mins + } + const processorList = await zrevrange(BANK_TRANSFER_PROCESSORS_SET_NAME, 0, -1, 'WITHSCORES'); + + const [highestScoredProcessor] = processorList; + + const bankTransferResult = processBankTransfer(highestScoredProcessor, amount); + if (!bankTransferResult.success) { + await zincrby(BANK_TRANSFER_PROCESSORS_SET_NAME, -10, highestScoredProcessor); + } else { + await zincrby(BANK_TRANSFER_PROCESSORS_SET_NAME, 10, highestScoredProcessor); + } + return bankTransferResult; +} + +// await trasferToBank(10); diff --git a/migrations/20210220170629-create-bank-transfer-processors-table.js b/migrations/20210220170629-create-bank-transfer-processors-table.js new file mode 100644 index 0000000..fca16d8 --- /dev/null +++ b/migrations/20210220170629-create-bank-transfer-processors-table.js @@ -0,0 +1,36 @@ +module.exports = { + up: async (queryInterface, Sequelize) => queryInterface.createTable('bank_transfer_processors', { + id: { + type: Sequelize.INTEGER, + allowNull: false, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + unique: true, + }, + position: { + type: Sequelize.INTEGER, + allowNull: false, + }, + enabled: { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + created_at: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.fn('NOW'), + }, + updated_at: { + allowNull: false, + type: Sequelize.DATE, + defaultValue: Sequelize.fn('NOW'), + }, + }), + + down: async (queryInterface, _Sequelize) => queryInterface.dropTable('bank_transfer_processors'), +}; diff --git a/models/bank_transfer_processors.js b/models/bank_transfer_processors.js new file mode 100644 index 0000000..680628d --- /dev/null +++ b/models/bank_transfer_processors.js @@ -0,0 +1,48 @@ +const { Model } = require('sequelize'); + +module.exports = (sequelize, DataTypes) => { + class BankTransferProcessors extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + } + BankTransferProcessors.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + position: { + type: DataTypes.INTEGER, + allowNull: false, + }, + enabled: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, + updated_at: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, + }, { + sequelize, + modelName: 'bank_transfer_processors', + underscored: true, + }); + return BankTransferProcessors; +}; diff --git a/package-lock.json b/package-lock.json index 1d4fa22..b2744a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2095,6 +2095,35 @@ "util-deprecate": "~1.0.1" } }, + "redis": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz", + "integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==", + "requires": { + "denque": "^1.4.1", + "redis-commands": "^1.5.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + } + }, + "redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "requires": { + "redis-errors": "^1.0.0" + } + }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", diff --git a/package.json b/package.json index 28b391e..d2b5f20 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "echo \"Error: no test specified\" && exit 1", "migrate": "sequelize db:migrate", "migrate:undo": "sequelize db:migrate:undo", - "migrate:undo:all": "sequelize db:migrate:undo:all" + "migrate:undo:all": "sequelize db:migrate:undo:all", + "seed": "sequelize db:seed:all" }, "keywords": [ "wallet", @@ -22,6 +23,7 @@ "dotenv": "^8.2.0", "joi": "^17.3.0", "mysql2": "^2.2.5", + "redis": "^3.0.2", "sequelize": "^6.3.5", "sequelize-cli": "^6.2.0", "uuid": "^8.3.2" diff --git a/redis.js b/redis.js new file mode 100644 index 0000000..1d609ad --- /dev/null +++ b/redis.js @@ -0,0 +1,18 @@ +const redis = require('redis'); +const { promisify } = require('util'); + +const client = redis.createClient(); + +client.on('error', (error) => { + console.error(error); +}); + +const zadd = promisify(client.zadd).bind(client); +const zrevrange = promisify(client.zrevrange).bind(client); +const exists = promisify(client.exists).bind(client); +const expire = promisify(client.expire).bind(client); +const zincrby = promisify(client.zincrby).bind(client); + +module.exports = { + zadd, zrevrange, exists, expire, zincrby, +}; diff --git a/seeders/20210220200445-populate-bank-transfer-processors-table.js b/seeders/20210220200445-populate-bank-transfer-processors-table.js new file mode 100644 index 0000000..aa80423 --- /dev/null +++ b/seeders/20210220200445-populate-bank-transfer-processors-table.js @@ -0,0 +1,30 @@ +module.exports = { + up: async (queryInterface, Sequelize) => queryInterface.bulkInsert('bank_transfer_processors', [ + { + name: 'monnify', + position: 1, + enabled: true, + created_at: Sequelize.literal('CURRENT_TIMESTAMP(3)'), + updated_at: Sequelize.literal('CURRENT_TIMESTAMP(3)'), + }, + { + name: 'paystack', + position: 2, + enabled: true, + created_at: Sequelize.literal('CURRENT_TIMESTAMP(3)'), + updated_at: Sequelize.literal('CURRENT_TIMESTAMP(3)'), + }, + { + name: 'flutterwave', + position: 3, + enabled: true, + created_at: Sequelize.literal('CURRENT_TIMESTAMP(3)'), + updated_at: Sequelize.literal('CURRENT_TIMESTAMP(3)'), + }, + ], + { + updateOnDuplicate: ['name'], + }), + + down: async (queryInterface, _Sequelize) => queryInterface.bulkDelete('bank_transfer_processors', null), +};