init commit

This commit is contained in:
modeht
2022-10-03 19:59:51 +02:00
commit 057e9306df
497 changed files with 109776 additions and 0 deletions
View File
View File
+289
View File
@@ -0,0 +1,289 @@
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
/**
* Auth Service
* @copyright 2021 Manaknightdigital Inc.
* @link https://manaknightdigital.com
* @license Proprietary Software licensing
* @author Ryan Wong
*
*/
const passwordService = require("./PasswordService");
const mailService = require("./MailService");
const generateCode = require("../utils/generateCode");
const db = require("../models");
const errors = require("../core/errors");
module.exports = {
/**
* Register new user with email and password
* @name authService.register
* @param {String} email user new email address
* @param {String} password user new password
* @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
* @example
* const payload = await authService.register(req.body.email, req.body.password)
*/
register: async function (email, password, roleId, userDetails = {}) {
try {
const isEmailAddressExist = await db.credential.getByFields({
email,
});
if (isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_ALREADY_EXIST);
const hashedPassword = await passwordService.hash(password);
var user = await db.user.insert({ ...userDetails, role_id: roleId }, { returnAllFields: true });
var credential = await db.credential.insert(
{
email: email,
password: hashedPassword,
user_id: user.id,
type: 0,
verify: 0,
status: 1,
},
{ returnAllFields: true }
);
return { credential: credential.id, user: user.id };
} catch (error) {
if (credential) {
await db.credential.realDelete(credential.id);
}
if (user) {
await db.user.realDelete(user.id);
}
console.error(error);
throw error;
}
},
/**
* Login user with email and password
* @name authService.login
* @param {String} email user email address
* @param {String} password user password
* @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
* @example
* const payload = await authService.login(req.body.email, req.body.password)
*/
login: async function (email, password, roldId) {
try {
const isEmailAddressExist = await db.credential.findOne({
where: { email, status: 1, type: 0 },
include: [{ model: db.user, required: true, as: "user", where: { role_id: roldId } }],
});
if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND);
const { password: hashedPassword, id, user_id } = isEmailAddressExist;
const user = await db.user.getByPK(user_id);
if (!user) {
throw new Error(errors.USER_NOT_FOUND);
}
const isPasswordMatch = await passwordService.compareHash(password, hashedPassword);
if (!isPasswordMatch) throw new Error(errors.INVALID_EMAIL_OR_PASSWORD);
return { credential: id, user: user.id };
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Send email and save to database
* @name authService.forgotPassword
* @param {String} email user email address
* @return {Promise.<Void>}
* @example
* await authService.forgotPassword(req.body.email)
*/
forgotPassword: async function (email) {
try {
const isEmailAddressExist = await db.credential.getByFields({
email: email,
});
if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND);
const { user_id } = isEmailAddressExist;
const getUser = await db.user.getByPK(user_id);
const verificationCode = generateCode(6);
mailService.initialize({
hostname: process.env.EMAIL_SMTP_SMTP_HOST,
port: process.env.EMAIL_SMTP_SMTP_PORT,
username: process.env.EMAIL_SMTP_SMTP_USER,
password: EMAIL_SMTP_SMTP_PASS,
from: process.env.MAIL_FROM,
to: email,
});
const mailTemplate = await mailService.template("reset-password");
const injectedMailTemplate = mailService.inject(
{
body: mailTemplate.body,
subject: mailTemplate.subject,
},
{
username: `${getUser.first_name} ${getUser.last_name}`,
verification_code: verificationCode,
}
);
await mailService.send(injectedMailTemplate);
await db.token.insert({ token: verificationCode, user_id });
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Verify forgot password confirmation code
* @name authService.verifyForgotPassword
* @param {code} code confirmation code
* @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
* @example
* const payload = await authService.verifyForgotPassword(req.body.code)
*/
verifyForgotPassword: async function (code) {
try {
const Token = await db.token.getByFields({
token: code,
});
const Credential = await db.credential.getByFields({
user_id: Token.user_id,
});
return { credential: Credential.id, user: Credential.user_id };
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Reset password
* @name authService.resetPassword
* @param {String} password user new password
* @param {String} credential_id user credential id
* @example
* await authService.resetPassword(req.body.password, credential_id)
*/
resetPassword: async function (password, credential_id) {
try {
const hashedPassword = await passwordService.hash(password);
await db.credential.edit(
{
password: hashedPassword,
},
credential_id
);
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Email confirmation
* @name authService.emailConfirmation
* @param {String} email user email address
* @example
* await authService.emailConfirmation(email)
*/
emailConfirmation: async function (email) {
try {
const isEmailAddressExist = await db.credential.getByFields({
email: email,
});
if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND);
const { user_id } = isEmailAddressExist;
const user = await db.user.getByPK(user_id);
const confirmationCode = generateCode(6);
mailService.initialize({
hostname: process.env.EMAIL_SMTP_SMTP_HOST,
port: process.env.EMAIL_SMTP_SMTP_PORT,
username: process.env.EMAIL_SMTP_SMTP_USER,
password: EMAIL_SMTP_SMTP_PASS,
from: process.env.MAIL_FROM,
to: email,
});
const mailTemplate = await mailService.template("email-confirmation");
const injectedMailTemplate = mailService.inject(
{
body: mailTemplate.body,
subject: mailTemplate.subject,
},
{
username: `${user.first_name} ${user.last_name}`,
confirmation_code: confirmationCode,
}
);
await mailService.send(injectedMailTemplate);
await db.token.insert({ token: confirmationCode, user_id, type: 6 });
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Verify Email address
* @name authService.emailVerify
* @param {String} token email confirmation code
* @param {string} user_id user id
* @example
* await authService.emailVerify(email, user_id)
*/
emailVerify: async function (token, user_id) {
try {
const isTokenExist = await db.token.getByFields({
user_id,
token,
type: 6,
});
if (!isTokenExist) throw new Error(errors.INVALID_EMAIL_CONFIRMATION_CODE);
await db.token.realDelete(isTokenExist.id);
} catch (error) {
console.error(error);
throw error;
}
},
/**
* check if user need to change password before logging in
* @name authService.forcePasswordChange
* @param {string} user_id user id
*/
forcePasswordChange: async function (user_id) {
try {
const { profile_id } = await db.user.getByPK(user_id);
const { force_password_change } = await db.profile.getByPK(profile_id);
if (force_password_change) return true;
else return false;
} catch (error) {
console.error(error);
throw error;
}
},
};
+277
View File
@@ -0,0 +1,277 @@
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
/**
* Auth Service
* @copyright 2021 Manaknightdigital Inc.
* @link https://manaknightdigital.com
* @license Proprietary Software licensing
* @author Ryan Wong
*
*/
const passwordService = require('./PasswordService');
const mailService = require('./MailService');
const generateCode = require('../utils/generateCode');
const db = require('../models');
const errors = {
EMAIL_ADDRESS_NOT_FOUND: 'xyzEMAIL_ADDRESS_NOT_FOUND',
EMAIL_ADDRESS_ALREADY_EXIST: 'xyzEMAIL_ADDRESS_ALREADY_EXIST',
PASSWORD_NOT_MATCH: 'xyzPASSWORD_NOT_MATCH',
INVALID_EMAIL_CONFIRMATION_CODE: 'xyzINVALID_EMAIL_CONFIRMATION_CODE',
INVALID_EMAIL_OR_PASSWORD: 'xyzINVALID_EMAIL_OR_PASSWORD',
};
module.exports = {
/**
* Register new user with email and password
* @name authService.register
* @param {String} email user new email address
* @param {String} password user new password
* @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
* @example
* const payload = await authService.register(req.body.email, req.body.password)
*/
register: async function (email, password, role_id, user_details = {}) {
try {
const isEmailAddressExist = await db.user.getByFields({
email,
});
if (isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_ALREADY_EXIST);
const hashedPassword = await passwordService.hash(password);
var user = await db.user.insert(
{
...user_details,
email: email,
password: hashedPassword,
role_id,
type: 0,
verify: 1,
status: 1,
},
{ returnAllFields: true },
);
return { user };
} catch (error) {
if (user) {
await db.user.realDelete(user.id);
}
console.error(error);
throw error;
}
},
/**
* Login user with email and password
* @name authService.login
* @param {String} email user email address
* @param {String} password user password
* @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
* @example
* const payload = await authService.login(req.body.email, req.body.password)
*/
login: async function (email, password, role_id) {
try {
const user = await db.user.getByFields({
email,
status: 1,
role_id,
type: 0,
});
if (!user) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND);
const { password: hashedPassword } = user;
const isPasswordMatch = await passwordService.compareHash(password, hashedPassword);
if (!isPasswordMatch) throw new Error(errors.INVALID_EMAIL_OR_PASSWORD);
return { user: user };
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Send email and save to database
* @name authService.forgotPassword
* @param {String} email user email address
* @return {Promise.<Void>}
* @example
* await authService.forgotPassword(req.body.email)
*/
forgotPassword: async function (email) {
try {
const user = await db.user.getByFields({
email: email,
});
if (!user) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND);
const verificationCode = generateCode(6);
mailService.initialize({
hostname: process.env.EMAIL_SMTP_SMTP_HOST,
port: process.env.EMAIL_SMTP_SMTP_PORT,
username: process.env.EMAIL_SMTP_SMTP_USER,
password: EMAIL_SMTP_SMTP_PASS,
from: process.env.MAIL_FROM,
to: email,
});
const mailTemplate = await mailService.template('reset-password');
const injectedMailTemplate = mailService.inject(
{
body: mailTemplate.body,
subject: mailTemplate.subject,
},
{
username: `${user.first_name} ${user.last_name}`,
verification_code: verificationCode,
},
);
await mailService.send(injectedMailTemplate);
await db.token.insert({ token: verificationCode, user_id: user.id });
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Verify forgot password confirmation code
* @name authService.verifyForgotPassword
* @param {code} code confirmation code
* @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
* @example
* const payload = await authService.verifyForgotPassword(req.body.code)
*/
verifyForgotPassword: async function (code) {
try {
const Token = await db.token.findByFields({
token: code,
});
const user = await db.user.getLast({ user_id: Token.user_id });
return { user };
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Reset password
* @name authService.resetPassword
* @param {String} password user new password
* @param {String} credential_id user credential id
* @example
* await authService.resetPassword(req.body.password, credential_id)
*/
resetPassword: async function (password, userId) {
try {
const hashedPassword = await passwordService.hash(password);
await db.user.edit(
{
password: hashedPassword,
},
userId,
);
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Email confirmation
* @name authService.emailConfirmation
* @param {String} email user email address
* @example
* await authService.emailConfirmation(email)
*/
emailConfirmation: async function (email) {
try {
const user = await db.user.getByFields({
email: email,
});
if (!user) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND);
const confirmationCode = generateCode(6);
mailService.initialize({
hostname: process.env.EMAIL_SMTP_SMTP_HOST,
port: process.env.EMAIL_SMTP_SMTP_PORT,
username: process.env.EMAIL_SMTP_SMTP_USER,
password: EMAIL_SMTP_SMTP_PASS,
from: process.env.MAIL_FROM,
to: email,
});
const mailTemplate = await mailService.template('email-confirmation');
const injectedMailTemplate = mailService.inject(
{
body: mailTemplate.body,
subject: mailTemplate.subject,
},
{
username: `${user.first_name} ${user.last_name}`,
confirmation_code: confirmationCode,
},
);
await mailService.send(injectedMailTemplate);
await db.token.insert({ token: confirmationCode, user_id: user.id, type: 6 });
} catch (error) {
console.error(error);
throw error;
}
},
/**
* Verify Email address
* @name authService.emailVerify
* @param {String} token email confirmation code
* @param {string} user_id user id
* @example
* await authService.emailVerify(email, user_id)
*/
emailVerify: async function (token, user_id) {
try {
const isTokenExist = await db.token.getByFields({
user_id,
token,
type: 6,
});
if (!isTokenExist) throw new Error(errors.INVALID_EMAIL_CONFIRMATION_CODE);
await db.token.realDelete(isTokenExist.id);
} catch (error) {
console.error(error);
throw error;
}
},
/**
* check if user need to change password before logging in
* @name authService.forcePasswordChange
* @param {string} user_id user id
*/
forcePasswordChange: async function (user_id) {
try {
const { profile_id } = await db.user.getByPk(user_id);
const { force_password_change } = await db.profile.getByPk(profile_id);
if (force_password_change) return true;
else return false;
} catch (error) {
console.error(error);
throw error;
}
},
};
+24
View File
@@ -0,0 +1,24 @@
const barcode = require('barcode');
module.exports = {
/**
* Generate barcode
* @param {string} string barcode text
* @param {{width?: number, height?: number}} param1 width and height for barcode image
* @returns {Promise.<string>}
*/
generateBarcode: async function (string, { width = 400, height = 100 }) {
return new Promise((resolve, reject) => {
const code128 = barcode('Code128', {
data: string,
width,
height,
});
code128.getBase64(function (error, base64String) {
if (error) reject(error);
else resolve(base64String);
});
});
},
};
+84
View File
@@ -0,0 +1,84 @@
const multer = require('multer');
const converter = require('json-2-csv');
const fs = require('fs');
const path = require('path');
const csv = require('fast-csv');
const db = require('./../models');
const upload_folder = path.resolve(__dirname, '../uploads');
module.exports = {
/**
* Import CSV file
* @param {request} req
* @param {response} res
*/
csv_preview: function (req, res) {
return new Promise((resolve, reject) => {
const upload = multer({ dest: path.resolve(upload_folder) }).single('file');
upload(req, res, async function (error) {
if (error) {
reject(error);
}
let data = [];
fs.createReadStream(path.resolve(upload_folder, req.file.path))
.pipe(csv.parse())
.on('error', (error) => reject(error))
.on('data', (row) => {
data.push(row);
})
.on('end', (rowCount) => {
fs.unlinkSync(path.resolve(upload_folder, req.file.path));
console.log(`Parsed ${rowCount} rows`);
resolve(data);
});
});
});
},
csv_import: async function (req, res) {
return new Promise((resolve, reject) => {
const table = req.params.model;
const upload = multer({ dest: path.resolve(upload_folder) }).single('file');
let data = [];
upload(req, res, async function (error) {
if (error) {
throw new Error(error);
}
fs.createReadStream(path.resolve(upload_folder, req.file.path))
.pipe(csv.parse({ headers: true }))
.on('error', (error) => reject(error))
.on('data', (row) => {
data.push(row);
})
.on('end', async (rowCount) => {
fs.unlinkSync(path.resolve(upload_folder, req.file.path));
console.log(`Parsed ${rowCount} rows`);
await db[table].batchInsert(data).catch((error) => {
console.log(error);
reject(error);
});
resolve(data);
});
});
});
},
/**
* Export CSV file
* @param {request} req
* @param {response} res
*/
csv_export: async function (req, res) {
try {
let fields = await db[req.table].getAll(req.where);
fields = JSON.stringify(fields); // do this needed?
const csv = await converter.json2csvAsync(fields);
res.header('Content-Type', 'text/csv');
res.attachment(req.table + '.csv');
return res.send(csv);
} catch (error) {
console.log(error);
}
},
};
+15
View File
@@ -0,0 +1,15 @@
const crypto = require('crypto');
module.exports = {
iv: crypto.randomBytes(16).toString('hex').slice(0, 16),
encrypt: function (message, secret) {
const encrypter = crypto.createCipheriv('aes-256-cbc', secret, this.iv);
let encryptMessage = encrypter.update(message, 'utf-8', 'hex');
return (encryptMessage += encrypter.final('hex'));
},
decrypt: function (encryptMessage, secret) {
const decrypter = crypto.createDecipheriv('aes-256-cbc', secret, this.iv);
let decryptedMessage = decrypter.update(encryptMessage, 'hex', 'utf8');
return (decryptedMessage += decrypter.final('utf8'));
},
};
+166
View File
@@ -0,0 +1,166 @@
var firebase = require('firebase-admin');
var serviceAccount = require('./serviceAccountKey.json');
/*
Examples:
Write:
firebaseService.write('notification', 1, {
a: 1,
b: 'd',
c:['a','w'],
d: [{a:1},{b:2}]
})
Read Once:
firebaseService.read('notification', 1).then(function(notification){
console.log(notification);
});
Update:
firebaseService.update('notification', 1, {a:2});
usersRef.update({
"alanisawesome/nickname": "Alan The Machine",
"gracehop/nickname": "Amazing Grace"
});
Read On Listeners:
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
Read Previous Listener:
ref.on("child_added", function(snapshot, prevChildKey) {
var newPost = snapshot.val();
console.log("Author: " + newPost.author);
console.log("Title: " + newPost.title);
console.log("Previous Post ID: " + prevChildKey);
});
Child Changed Listener:
// Get the data on a post that has changed
ref.on("child_changed", function(snapshot) {
var changedPost = snapshot.val();
console.log("The updated post title is " + changedPost.title);
});
Child Removed Listener:
// Get the data on a post that has been removed
ref.on("child_removed", function(snapshot) {
var deletedPost = snapshot.val();
console.log("The blog post titled '" + deletedPost.title + "' has been deleted");
});
Order By Listener:
ref.orderByChild("height").on("child_added", function(snapshot) {
console.log(snapshot.key + " was " + snapshot.val().height + " meters tall");
});
Order By key:
var ref = db.ref("dinosaurs");
ref.orderByKey().on("child_added", function(snapshot) {
console.log(snapshot.key);
});
Limited:
var ref = db.ref("dinosaurs");
ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) {
console.log(snapshot.key);
});
var ref = db.ref("dinosaurs");
ref.orderByChild("height").startAt(3).on("child_added", function(snapshot) {
console.log(snapshot.key);
});
var ref = db.ref("dinosaurs");
ref.orderByKey().endAt("pterodactyl").on("child_added", function(snapshot) {
console.log(snapshot.key);
});
var ref = db.ref("dinosaurs");
ref.orderByKey().startAt("b").endAt("b\uf8ff").on("child_added", function(snapshot) {
console.log(snapshot.key);
});
Equal To:
var ref = db.ref("dinosaurs");
ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) {
console.log(snapshot.key);
});
*/
function FirebaseService() {
this._transporter = firebase.initializeApp(
{
// credential: firebase.credential.cert('./serviceAccountKey.json'),
credential: firebase.credential.cert(serviceAccount),
databaseURL: 'https://konfer-243320.firebaseio.com',
},
// , 'flashbid-prod'
);
console.log('Prod Firebase');
this._database = this._transporter.database();
this._messaging = this._transporter.messaging();
this.write = function (table, id, payload) {
return this._database.ref(table + '/' + id).set(payload);
};
this.read = function (table, id) {
return this._database
.ref(table + '/' + id)
.once('value')
.then(function (snapshot) {
return snapshot.val();
});
};
this.update = function (table, id, payload) {
const ref = this._database.ref(table).child(id);
ref.update(payload);
};
this.updateSpecific = function (table, id, payload) {
return this._database.ref(table + '/' + id).update(payload);
};
this.pushToList = function (table, id, listField, payload) {
const ref = this._database.ref(table).child(id).child(listField).push();
ref.set(payload);
};
this.sendPushNotification = function (
id,
title,
subs,
description,
deviceId,
) {
// This registration token comes from the client FCM SDKs.
// See the "Defining the message payload" section above for details
// on how to define a message payload.
var payload = {
notification: {
title: title,
body: description,
},
data: {
title: subs.text,
body: subs.desc,
action: subs.action,
id: id,
},
};
// Set the message as high priority and have it expire after 24 hours.
var options = {
priority: 'high',
timeToLive: 60 * 60 * 24,
};
console.log('payload', payload);
console.log('device id', deviceId);
// Send a message to the device corresponding to the provided
// registration token with the provided options.
return this._messaging.sendToDevice(deviceId, payload, options);
};
}
module.exports = FirebaseService;
+30
View File
@@ -0,0 +1,30 @@
const html_to_pdf = require('html-pdf-node');
module.exports = {
html_to_pdf_with_content: async function (content, options) {
const file = { content };
return new Promise(function (resolve, reject) {
html_to_pdf
.generatePdf(file, options)
.then((pdfBuffer) => {
resolve(pdfBuffer);
})
.catch((error) => reject(error));
});
},
html_to_pdf_with_url: async function (url, options) {
const file = { url };
return new Promise(function (resolve, reject) {
html_to_pdf
.generatePdf(file, options)
.then((pdfBuffer) => {
resolve(pdfBuffer);
})
.catch((error) => reject(error));
});
},
};
// pdf to html https://www.npmjs.com/package/pdf2html
+108
View File
@@ -0,0 +1,108 @@
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/**
* JWT Service
* @copyright 2020 Manaknightdigital Inc.
* @link https://manaknightdigital.com
* @license Proprietary Software licensing
* @author Ryan Wong
*
*/
var jwt = require('jsonwebtoken');
var dotenv = require('dotenv');
dotenv.config();
dotenv.config({ path: './src/.env' });
module.exports = {
createAccessToken: function (
payload,
expireIn = process.env.DYNAMIC_CONFIG_JWT_EXPIRE_AT,
) {
const secret = process.env.DYNAMIC_CONFIG_JWT_KEY;
return jwt.sign(payload, secret, {
expiresIn: Number(expireIn),
algorithm: 'HS256',
});
},
createRefreshToken: function (payload) {
return jwt.sign(payload, process.env.DYNAMIC_CONFIG_JWT_KEY, {
expiresIn: Number(process.env.DYNAMIC_CONFIG_JWT_REFRESH_EXPIRE_AT),
algorithm: 'HS256',
});
},
verifyAccessToken: function (token) {
try {
return jwt.verify(token, process.env.DYNAMIC_CONFIG_JWT_KEY);
} catch (err) {
return false;
}
},
verifyRefreshToken: function (token) {
try {
return jwt.verify(token, process.env.DYNAMIC_CONFIG_JWT_KEY);
} catch (err) {
return false;
}
},
generateString: function (length) {
let d = new Date().getTime();
const time = new Date().getTime();
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx-xxxx'.replace(
/[xy]/g,
function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x7) | 0x8).toString(16);
},
);
return (uuid.toUpperCase() + '-' + time.toString()).substring(0, length);
},
generateUUID: function () {
let d = new Date().getTime();
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x7) | 0x8).toString(16);
},
);
return uuid.toUpperCase();
},
getToken: function (req) {
if (
req.headers.authorization &&
req.headers.authorization.split(' ')[0] === 'Bearer'
) {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
},
verifyTokenMiddleware: function (role) {
const self = this;
return function (req, res, next) {
const token = self.getToken(req);
if (!token) {
return res.status(401).json({
success: false,
code: 'UNAUTHORIZED',
});
} else {
const result = self.verifyAccessToken(token);
if (!result) {
return res.status(401).json({
success: false,
code: 'TOKEN_EXPIRED',
});
}
req.user_id = result;
next();
}
};
},
};
+37
View File
@@ -0,0 +1,37 @@
const winston = require("winston");
winston.level = process.env.LOG_LEVEL;
const tsFormat = () => new Date().toLocaleString();
const logger = winston.createLogger({
level: "info",
format: winston.format.json(),
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
new winston.transports.File({
filename: "error.log",
level: "error",
timestamp: tsFormat,
}),
new winston.transports.File({
filename: "combined.log",
timestamp: tsFormat,
}),
],
});
//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
if (process.env.NODE_ENV != "production") {
logger.add(
new winston.transports.Console({
format: winston.format.simple(),
timestamp: tsFormat,
colorize: true,
})
);
}
module.exports = logger;
+99
View File
@@ -0,0 +1,99 @@
const nodemailer = require('nodemailer');
const db = require('../models');
module.exports = {
/** @private */
transport: null,
/** @private */
from: null,
/** @private */
to: null,
/**
* Nodemailer initializer
* @name mailService.initialize
* @param {{hostname: String, port: Number, username: String, password: String, from: String, to: String}} config Nodemailer configuration
* @returns {Void}
*/
initialize: function (config) {
this.transport = nodemailer.createTransport({
host: config.hostname,
port: config.port,
auth: {
user: config.username,
pass: config.password,
},
});
this.from = config.from;
this.to = config.to;
},
/**
* Get email template from database
* @name mailService.template
* @param {String} slug email template slug
* @reject {Error}
* @returns {Promise.<{body: String, subject: String}>} email template
*/
template: function (slug) {
return new Promise(function (resolve, reject) {
db.email
.findOne({ where: { slug } })
.then((response) => {
if (!response) {
return reject(`TEMPLATE_NOT_FOUND`);
} else resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
/**
* Inject values into email template
* @name mailService.inject
* @param {{body: String, subject: String}} template email template
* @param {Object.<string, string>} payload template values
* @returns {{from: String, to: String, subject: String, text: String}} Value injected email template
*/
inject: function (template, payload) {
let mailBody = template.body;
let mailSubject = template.subject;
for (const key in payload) {
const value = payload[key];
mailBody = mailBody.replace(new RegExp('{{{' + key + '}}}', 'g'), value);
}
for (const key in payload) {
const value = payload[key];
mailSubject = mailSubject.replace(
new RegExp('{{{' + key + '}}}', 'g'),
value,
);
}
return {
from: this.from,
to: this.to,
subject: mailSubject,
html: mailBody,
};
},
/**
* Send email
* @name mailService.send
* @param {nodemailer.SendMailOptions} template email template
* @reject {Error} send mail error
* @returns {Promise.<nodemailer.SentMessageInfo>} send mail info
*/
send: function (template) {
let self = this;
return new Promise(function (resolve, reject) {
self.transport
.sendMail(template)
.then((response) => resolve(response))
.catch((error) => reject(error));
});
},
};
View File
+200
View File
@@ -0,0 +1,200 @@
const querystring = require('querystring');
const axios = require('axios').default;
const db = require('../models');
module.exports = {
google: {
/**
* Generates authentication URL
* @name oAuth.google.generateAuthURL
* @param {{redirect_uri: String, client_id: String}} config
* @returns {String} authentication URL
*/
generateAuthURL(config) {
const rootUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
const options = {
redirect_uri: config.redirect_uri,
client_id: config.client_id,
access_type: 'offline',
response_type: 'code',
prompt: 'consent',
scope: ['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email'].join(' '),
};
return `${rootUrl}?${querystring.stringify(options)}`;
},
/**
* Generates authentication Token
* @name oAuth.google.generateAuthToken
* @param {{auth_code: String, client_id: String, client_secret: String, redirect_uri: String}} config
* @returns {Promise.<{{access_token: String, expires_in: String, refresh_token: String, scope: String, token_type: String id_token: String}}>} authentication token
*/
generateAuthToken(config) {
return new Promise(function (resolve, reject) {
const url = 'https://oauth2.googleapis.com/token';
const buildQuerystring = querystring.stringify({
code: config.auth_code,
client_id: config.client_id,
client_secret: config.client_secret,
redirect_uri: config.redirect_uri,
grant_type: 'authorization_code',
});
axios.default
.post(url, buildQuerystring, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
.then((response) => resolve(response.data))
.catch((error) => reject(error));
});
},
/**
* Retrieve user information
* @name oAuth.google.getUserInfo
* @param {{access_token: String, expires_in: String, refresh_token: String, scope: String, id_token: String}} config
* @returns {Promise.<{id: string, email: string, verified_email: true, name: string, given_name: string, family_name: string, picture: string, locale: string}>} user information
*/
getUserInfo(config, isAPI = false) {
return new Promise(function (resolve, reject) {
axios
.get(
`https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${config.access_token}`,
isAPI
? {}
: {
headers: {
Authorization: `Bearer ${config.id_token}`,
},
},
)
.then((response) => resolve(response.data))
.catch((error) => reject(error));
});
},
},
facebook: {
/**
* Generates authentication URL
* @name oAuth.facebook.generateAuthURL
* @param {{redirect_uri: String, client_id: String}} config
* @returns {String} authentication URL
*/
generateAuthURL(config) {
const stringifiedParams = querystring.stringify({
client_id: config.client_id,
redirect_uri: process.env.DYNAMIC_CONFIG_FACEBOOK_REDIRECT_URI,
scope: ['email', 'user_friends'].join(','),
response_type: 'code',
auth_type: 'rerequest',
display: 'popup',
});
return `https://www.facebook.com/v4.0/dialog/oauth?${stringifiedParams}`;
},
/**
* Generates authentication Token
* @name oAuth.facebook.generateAuthToken
* @param {{auth_code: String, client_id: String, client_secret: String, redirect_uri: String}} config
* @returns {Promise.<{access_token: String, token_type: String, expires_in: String}>} authentication token
*/
async generateAuthToken(config) {
const url = 'https://graph.facebook.com/v4.0/oauth/access_token';
const { data } = await axios({
url,
method: 'GET',
params: {
code: config.auth_code,
client_id: config.client_id,
client_secret: config.client_secret,
redirect_uri: config.redirect_uri,
},
});
return data;
},
/**
* Retrieve user information
* @name oAuth.facebook.getUserInfo
* @param {{access_token: String, token_type: String, expires_in: String}} config
* @returns {Promise.<{id: string, email: string, first_name: string, last_name: string, image: string}>} user information
*/
async getUserInfo(config) {
const { data } = await axios({
url: 'https://graph.facebook.com/me',
method: 'GET',
params: {
fields: ['id', 'email', 'first_name', 'last_name'].join(','),
access_token: config.access_token,
},
});
return data;
},
},
async authenticate(user) {
let User;
let Credential;
try {
const isEmailExist = await db.credential.getByField('email', user.email);
if (isEmailExist) {
// Check status and role
if (+isEmailExist.status === 0 || +user.role === +isEmailExist.role) {
throw new Error('EMAIL_ADDRESS_NOT_FOUND');
}
// Check provider type type
const type = isEmailExist.type === user.provider;
if (!type) {
throw new Error('ACCOUNT_IS_REGISTERED_WITH_' + (user.provider === 'n' ? 'EMAIL_AND_PASSWORD' : user.provider === 'g' ? 'GOOGLE' : 'FACEBOOK'));
}
const userExists = await db.user.getByPK(isEmailExist.user_id);
if (!userExists) {
throw new Error('EMAIL_ADDRESS_NOT_FOUND');
}
return {
credential: isEmailExist.id,
user: userExists,
};
}
User = await db.user.insert(
{
first_name: user.first_name,
last_name: user.last_name,
image: user.image,
},
{ returnAllFields: true },
);
Credential = await db.credential.insert(
{
user_id: User.id,
email: user.email,
type: user.provider,
role_id: user.role_id,
status: 1,
},
{ returnAllFields: true },
);
return { credential: Credential.id, user: User };
} catch (error) {
if (User) {
User.destroy();
}
if (Credential) {
Credential.destroy();
}
throw new Error(error.message);
}
},
};
+64
View File
@@ -0,0 +1,64 @@
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/**
* Pagination Service
* @copyright 2020 Manaknightdigital Inc.
* @link https://manaknightdigital.com
* @license Proprietary Software licensing
* @author Ryan Wong
*
*/
module.exports = function (page, numItems) {
this.page = page ? Number(page) : 1;
this.numItems = numItems ? Number(numItems) : 20;
this.data = [];
this.count = 0;
this.numPages = 0;
this.getItems = function () {
return this.data;
};
this.getPage = function () {
return this.page;
};
this.getCount = function () {
return this.count;
};
this.setCount = function (count) {
this.count = count;
};
this.getNumPages = function () {
return this.numPages;
};
this.setItems = function (data) {
if (data.length == 1 && !data[0].id) {
this.count = 0;
this.numPages = 1;
this.data = [data[0]];
return this;
}
this.data = data;
this.numPages = this.count > 1 ? Math.ceil(this.count / this.numItems) : 1;
this.data = this.data.map(function (transaction) {
delete transaction.num;
return transaction.toJSON();
});
return this;
};
this.getOffset = function () {
return (this.page - 1) * this.numItems;
};
this.getLimit = function () {
return this.numItems;
};
return this;
};
+22
View File
@@ -0,0 +1,22 @@
const bcrypt = require('bcryptjs');
module.exports = {
/**
* Hash password
* @param {String} password password string to hash
* @returns {Promise.<String>} hashed password
*/
hash: async function (password) {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
},
/**
* Compare hashed password with password string
* @param {String} password password string
* @param {String} hashedPassword hashed password
* @returns {Promise.<Boolean>} return true if hashed password match with password string else return false
*/
compareHash: async function (password, hashedPassword) {
return await bcrypt.compare(password, hashedPassword);
},
};
+680
View File
@@ -0,0 +1,680 @@
require('dotenv').config(); //require .env for stripe configurations
const moment = require('moment');
const stripe = require('./StripeApi'); //this is Payment Service
const paypal = require('./PaypalApi');
const db = require('../models');
const userModel = db.user;
const stripeSubscriptionsModel = db.stripe_subscriptions;
const stripeSubscriptionsLogModel = db.stripe_subscriptions_log;
const paymentPlansModel = db.payment_plans;
const stripeProductsModel = db.stripe_products;
const paymentServicesModel = db.payment_services;
const stripeCardsModel = db.stripe_cards;
module.exports = new Service();
function Service() {
this.userId = 0;
this.roleId = 0;
this.currency = process.env.STRIPE_CURRENCY;
this.prorate = process.env.STRIPE_PRORATE;
this.forceCancel = process.env.STRIPE_FORCE_CANCEL;
this.setUserId = function (userId) {
this.userId = userId;
};
this.setRoleId = function (roleId) {
this.roleId = roleId;
};
this.setCurrency = function (currency) {
this.currency = currency;
};
this.setProrationType = function (prorate) {
this.prorate = prorate;
};
this.setCancelType = function (forceCancel) {
this.forceCancel = forceCancel;
};
let convertToCents = function (amount) {
return amount * 100;
};
let handleDBError = function (error) {
console.error(error);
throw new Error('Internal error: Database error');
};
let handleRequestError = function (error) {
console.error(error);
throw new Error('Internal error: Request error');
};
this.subscribe = async function (subscriptionParams, plan) {
var subscription = await stripe.createSubscription(subscriptionParams);
//create model entry
let subscriptionModelEntry = {
stripe_id: subscription.id ?? '',
cancel_at_period_end: subscription.cancel_at_period_end ?? null,
current_period_start: moment(new Date(subscription.current_period_start * 1000)).format('YYYY-MM-DD') ?? null,
current_period_end: moment(new Date(subscription.current_period_end * 1000)).format('YYYY-MM-DD') ?? null,
user_id: this.userId ?? null,
role_id: this.roleId ?? null,
plan_id: plan?.id ?? null,
coupon_stripe_id: subscription.discount?.coupon?.id ?? '',
customer_stripe_id: subscription.customer ?? '',
collection_method: subscription.collection_method ?? '',
interval: stripeSubscriptionsModel.inverse_interval_mapping(subscription.plan?.interval) ?? 0,
interval_count: subscription.plan?.interval_count ?? '',
trial_period_days: subscription.plan?.trial_period_days ?? 0,
trial_end: subscription.trial_end ? moment(new Date(subscription.trial_end * 1000)).format('YYYY-MM-DD') : null,
trial_start: subscription.trial_start ? moment(new Date(subscription.subscriptiontrial_start * 1000)).format('YYYY-MM-DD') : null,
status: stripeSubscriptionsModel.inverse_status_mapping(subscription.status) ?? null,
};
let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
if (!subscriptionCreatedId) {
throw new Error('Subscription is not found.');
}
let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, subscription.status, plan);
if (!subscriptionLogUpdated) {
throw new Error('Error while editing subscription log');
}
return subscriptionCreatedId;
};
this.updateSubscriptionLog = async function (subscriptionCreatedId, subscriptionCreatedStatus, plan) {
let subscriptionLog = await stripeSubscriptionsLogModel.getLast({ user_id: this.userId, role_id: this.roleId });
if (!subscriptionLog) {
let subscriptionLogModelEntry = {
user_id: this.userId,
role_id: this.roleId,
plan_id: plan?.id ?? null,
subscription_id: subscriptionCreatedId,
type: plan?.type ?? null,
status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
};
if (!(await stripeSubscriptionsLogModel.insert(subscriptionLogModelEntry))) {
throw new Error('Subscription log add not successfull');
}
} else {
let subscriptionLogModelEntry = {
plan_id: plan?.id ?? null,
subscription_id: subscriptionCreatedId,
type: plan?.type ?? null,
status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
};
if (!(await stripeSubscriptionsLogModel.edit(subscriptionLogModelEntry, subscriptionLog.id))) {
throw new Error('Subscription log edit not successfull');
}
}
return true;
};
this.createRegularSubscription = async function (user, plan, card, coupon) {
let subscriptionParams = {
customer: user.stripe_id,
items: [{ price: plan.stripe_id }],
trial_from_plan: true,
default_payment_method: card.stripe_card_id,
coupon: coupon?.stripe_id ?? null,
};
return await this.subscribe(subscriptionParams, plan);
};
this.createLifetimeSubscription = async function (user, plan, card, coupon) {
//create invoice, finalize and pay it.
let lifetimePlanStripeObject = await stripe.retrievePrice(plan.stripe_id);
let invoiceItemParams = {
customer: user.stripe_id,
price: plan.stripe_id,
discounts: [{ coupon: coupon?.stripe_id }],
};
let invoiceItemCreated = await stripe.createInvoiceItem(invoiceItemParams);
let invoiceParams = {
customer: user.stripe_id,
default_payment_method: card?.stripe_card_id ?? null,
};
let invoiceCreated = await stripe.createInvoice(invoiceParams);
let invoicePaid = await stripe.payInvoice(invoiceCreated.id);
let subscriptionModelEntry = {
stripe_id: invoiceItemCreated.id ?? '',
cancel_at_period_end: null,
current_period_start: moment().format('YYYY-MM-DD') ?? null,
current_period_end: null,
user_id: this.userId ?? null,
role_id: this.roleId ?? null,
plan_id: plan?.id ?? null,
coupon_stripe_id: invoiceItemCreated.discounts.length > 0 ? coupon?.stripe_id : '',
customer_stripe_id: invoicePaid.customer ?? '',
collection_method: invoicePaid.collection_method ?? '',
interval: 4,
interval_count: null,
trial_period_days: 0,
trial_end: null,
trial_start: null,
status: 4,
};
let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
if (!subscriptionCreatedId) {
throw new Error('Subscription is not found.');
}
let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, 'active', plan);
if (!subscriptionLogUpdated) {
throw new Error('Error while editing subscription log');
}
return subscriptionCreatedId;
};
let calculateFullPeriod = function (interval, interval_count = 1) {
let totalNumberOfDays = paymentPlansModel.interval_days_mapping()[interval] * interval_count;
let cancelAtEpoch = moment().add(totalNumberOfDays, 'days').unix();
return cancelAtEpoch;
};
this.createTrialSubscription = async function (user, plan, card, coupon) {
let trialPlanFullPeriod = calculateFullPeriod(plan.interval, plan.interval_count);
let subscriptionParams = {
customer: user.stripe_id,
items: [{ price: plan.stripe_id }],
trial_from_plan: true,
default_payment_method: card.stripe_card_id,
coupon: coupon?.stripe_id ?? null,
cancel_at: trialPlanFullPeriod,
};
return await this.subscribe(subscriptionParams, plan);
};
this.cancelRegularSubscription = async function (subscription) {
if (this.forceCancel === 'true') {
var cancellationParams = {
subscriptionId: subscription.stripe_id,
params: {
invoice_now: true,
prorate: this.prorate,
},
};
let subscriptionCanceled = await stripe.cancelSubscription(cancellationParams);
let newStatus = stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status];
await stripeSubscriptionsModel.edit({ status: newStatus }, subscription.id);
} else {
let updateParams = {
subscriptionId: subscription.stripe_id,
params: {
cancel_at_period_end: true,
},
};
await stripe.updateSubscription(updateParams);
await stripeSubscriptionsModel.edit({ cancel_at_period_end: 1 }, subscription.id);
}
};
this.cancelLifetimeSubscription = async function (subscription) {
//TODO check if we refund on this or not
//we could make a fixed refund amount
//right now we just cancel it locally and refund is never as it is a onetime product thing.
let subscriptionEdited = await stripeSubscriptionsModel.edit({ status: 5 }, parseInt(subscription.id));
if (!subscriptionEdited) {
throw new Error("Couldn't edit subscription");
}
let subscriptionLog = await stripeSubscriptionsLogModel.getByFields({ subscription_id: parseInt(subscription.id), status: 1 });
if (!subscriptionLog) {
throw new Error("Couldn't find relative active subscription log");
}
let subscriptionLogEdited = await stripeSubscriptionsLogModel.edit({ status: 0 }, subscriptionLog.id);
if (subscriptionLogEdited) {
return true;
}
throw new Error("Couldn't Edit subscription log");
};
this.createCustomerWithoutCard = async function (params) {
let createdCustomer = await stripe.createCustomer(params);
let userModelEntry = {
stripe_id: createdCustomer.id,
};
let modelEditedUser = await this.userModel.edit(userModelEntry, this.userId);
if (!modelEditedUser) {
throw new Error('Internal error: Error editing user stripe id');
}
return modelEditedUser;
};
this.createCustomer = async function (params) {
let createdCustomer = await stripe.createCustomer(params);
let userModelEntry = {
stripe_id: createdCustomer.id,
};
let editedUserModel = await userModel.edit(userModelEntry, this.userId);
if (!editedUserModel) {
throw new Error('Error editing user stripe id');
}
let customerCardParams = {
customerId: createdCustomer.id,
cardId: createdCustomer.default_source,
};
let card = await stripe.retrieveCard(customerCardParams);
if (card) {
cardModelEntry = {
card_last: card.last4 ?? '',
card_brand: card.brand ?? '',
card_exp_month: card.exp_month ?? '',
exp_year: card.exp_year ?? '',
card_name: createdCustomer.metadata.card_name ?? '',
stripe_card_customer: card.customer,
stripe_card_id: card.id,
is_default: createdCustomer.metadata.is_default,
user_id: this.userId ?? null,
role_id: this.roleId ?? null,
};
var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
if (!modelCreatedCard) {
throw new Error('Internal error: Error adding card');
}
}
return modelCreatedCard;
};
this.adminCreateProductPrice = async function (productStripeId, price, productLocalId) {
let priceArgs = {
unit_amount: convertToCents(price),
currency: this.currency,
product: productStripeId,
};
let priceCreated = await stripe.createPrice(priceArgs);
let productModelEditEntry = {
price,
price_stripe_id: priceCreated.id,
};
let modelEditedProduct = await stripeProductsModel.edit(productModelEditEntry, productLocalId);
if (!modelEditedProduct) {
throw new Error('Internal Error: error setting price');
}
return priceCreated;
};
this.adminCreateProduct = async function (stripeProductParam, paypalProductParam, localProductType) {
if (!stripeProductParam.name) {
throw new Error('Must provide a name for your product');
}
await paypal.setAccessToken();
let createdStripeProduct = await stripe.createProduct(stripeProductParam);
if (localProductType == 0) {
let productModelEntry = {
stripe_id: createdStripeProduct.id,
name: createdStripeProduct.name,
status: createdStripeProduct.active ? 1 : 0,
description: createdStripeProduct.description ?? '',
images: createdStripeProduct.images.join(';;;'),
shippable: createdStripeProduct.shippable ?? 0,
unit_label: createdStripeProduct.unit_label ?? '',
statement_descriptor: createdStripeProduct.statement_descriptor ?? '',
};
let modelCreatedStripeProduct = await stripeProductsModel.insert(productModelEntry);
if (!modelCreatedStripeProduct) {
throw new Error('Internal error: error adding a product');
}
return {
productStripeId: createdStripeProduct.id,
productLocalId: modelCreatedStripeProduct,
};
} else if (localProductType == 1) {
let createdPaypalProduct = await paypal.createProduct(paypalProductParam);
let paymentServiceModelEntry = {
name: createdStripeProduct.name,
stripe_id: createdStripeProduct.id,
paypal_id: createdPaypalProduct.data.id,
status: createdStripeProduct.active ? 1 : 0,
image: createdStripeProduct.images.length > 0 ? createdStripeProduct.images[0] : '',
url: createdStripeProduct.url ?? '',
category: createdStripeProduct.metadata.category,
description: createdStripeProduct.description ?? '',
};
let modelCreatedServiceId = await paymentServicesModel.insert(paymentServiceModelEntry);
let service = paymentServicesModel.getByPK(modelCreatedServiceId);
return {
serviceProductStripeId: service.stripe_id,
serviceProductPaypalId: service.paypal_id,
serviceProductLocalId: service.id,
};
}
return false;
};
/**
* [adminCreateSubscriptionPlan allows an admin to create subscription plan through admin portal]
* @param {object} planParams [object that contain plan parameters]
* @param {integer} planType [integer that represent plan type in relative to stripe plans table] @see /models/stripe_plans.js type_mapping()
* @param {integer} localProductId [integer that represent product id in relative to stripe products table]
* @return {integer} [the id of the created plan in database]
*/
this.adminCreateSubscriptionPlan = async function (params, planType, localServiceId) {
if (!params.nickname) {
throw new Error('Must have a display name');
}
if (!localServiceId) {
throw new Error('Must provide system product id');
}
if (!planType) {
throw new Error('Must provide plan type');
}
await paypal.setAccessToken();
let service = await paymentServicesModel.getByPK(localServiceId);
let stripeSubscriptionPlanParams = {
nickname: params.nickname,
currency: process.env.STRIPE_CURRENCY,
unit_amount: convertToCents(params.amount),
product: service.stripe_id,
recurring: {
interval: paymentPlansModel.interval_mapping()[params.interval],
interval_count: params.interval_count,
trial_period_days: params.trial_period_days,
},
active: parseInt(params.status) === 1 ? true : false,
};
let paypalSubscriptionPlanParams = {
product_id: service.paypal_id,
name: params.nickname,
billing_cycles: [
{
frequency: {
interval_unit: paymentPlansModel.interval_mapping()[params.interval].toUpperCase(),
interval_count: params.interval_count,
},
tenure_type: 'REGULAR',
sequence: 1,
total_cycles: 0,
pricing_scheme: {
fixed_price: {
value: params.amount,
currency_code: this.currency.toUpperCase(),
},
},
},
],
payment_preferences: {
auto_bill_outstanding: true,
setup_fee_failure_action: 'CANCEL',
payment_failure_threshold: 0,
},
};
if (params.trial_period_days > 0) {
paypalSubscriptionPlanParams.billing_cycles[0].sequence = 2;
paypalSubscriptionPlanParams.billing_cycles.unshift({
frequency: {
interval_unit: 'DAY',
interval_count: parseInt(params.trial_period_days),
},
tenure_type: 'TRIAL',
sequence: 1,
total_cycles: 1,
});
}
let stripePlanCreated = await stripe.createPrice(stripeSubscriptionPlanParams);
let paypalPlanCreated = await paypal.createSubscriptionPlan(paypalSubscriptionPlanParams);
let planModelEntry = {
stripe_product_id: stripePlanCreated.product,
paypal_product_id: paypalPlanCreated.data.product_id,
stripe_id: stripePlanCreated.id,
paypal_id: paypalPlanCreated.data.id,
amount: stripePlanCreated.unit_amount / 100,
interval: paymentPlansModel.inverse_interval_mapping(stripePlanCreated.recurring.interval),
interval_count: stripePlanCreated.recurring.interval_count,
trial_period_days: stripePlanCreated.recurring.trial_period_days ?? 0,
nickname: stripePlanCreated.nickname,
service_id: localServiceId,
status: stripePlanCreated.active ? 1 : 0,
type: parseInt(planType),
};
let modelCreatedPlan = await paymentPlansModel.insert(planModelEntry);
if (!modelCreatedPlan) {
throw new Error('Internal error: error creating plan');
}
return modelCreatedPlan;
};
this._createStripeLifetimePlan = async function (params, service) {
//stripe doesn't have lifetime plans
//how it works is a price is created with no recurring parameter as a single time product
//which acts and dealt with internally within our system as a lifetime plan.
let stripeSubscriptionPlanParams = {
nickname: params.nickname,
currency: this.currency,
unit_amount: convertToCents(params.amount),
product: service.stripe_id,
active: parseInt(params.status) === 1 ? true : false,
};
return await stripe.createPrice(stripeSubscriptionPlanParams);
};
this._createPaypalLifetimePlan = async function (params, service) {
//paypal doesn't allow lifetime plan
//how it works is i create a blan with a trial with 999 years and setup fee for subscription as the main amount for the lifetime plan
//paypal needs to have a regular plan so after the 999 years there is a regular plan with 0.01 amount money
//this works for both lifetime paid and free plans.
await paypal.setAccessToken();
let paypalSubscriptionPlanParams = {
product_id: service.paypal_id,
name: params.nickname,
billing_cycles: [
{
frequency: {
interval_unit: 'YEAR',
interval_count: 1,
},
tenure_type: 'TRIAL',
sequence: 1,
total_cycles: 999,
},
{
frequency: {
interval_unit: 'YEAR',
interval_count: 1,
},
tenure_type: 'REGULAR',
sequence: 2,
total_cycles: 0,
pricing_scheme: {
fixed_price: {
value: '0.01',
currency_code: this.currency.toUpperCase(),
},
},
},
],
payment_preferences: {
auto_bill_outstanding: true,
setup_fee: {
value: params.amount,
currency_code: this.currency.toUpperCase(),
},
setup_fee_failure_action: 'CANCEL',
payment_failure_threshold: 0,
},
};
return await paypal.createPlan(paypalSubscriptionPlanParams);
};
this._destroyStripeLifetimePlan = async function (planStripeId) {
//stripe doesn't allow deleting a price. will be set to unactive instead
return await stripe.updatePrice({ priceId: planStripeId, params: { active: false } });
};
this._destroyPaypalLifetimePlan = async function (planPaypalId) {
//paypal doesn't allow deleting a price. will be set to unactive instead
return await paypal.deactivatePlan(planPaypalId);
};
this.adminCreateLifetimePlan = async function (params, planType, localServiceId) {
if (!params.nickname) {
throw new Error('Must have a display name');
}
if (!localServiceId) {
throw new Error('Must provide system product id');
}
if (!planType) {
throw new Error('Must provide plan type');
}
if (parseInt(params.interval) !== 4) {
throw new Error('Interval must be "forever".');
}
let service = await paymentServicesModel.getByPK(localServiceId);
let stripePlanCreated = await this._createStripeLifetimePlan(params, service);
let paypalPlanCreated = await this._createPaypalLifetimePlan(params, service);
if (!stripePlanCreated && paypalPlanCreated) {
await this._destroyPaypalLifetimePlan(paypalPlanCreated.data.id);
throw new Error('Internal Error: Something happended while creating the plans.');
}
if (!paypalPlanCreated && stripePlanCreated) {
await this._destroyStripeLifetimePlan(stripePlanCreated.id);
throw new Error('Internal Error: Something happended while creating the plans.');
}
let planModelEntry = {
stripe_product_id: stripePlanCreated?.product ?? '',
paypal_product_id: paypalPlanCreated?.data?.product_id ?? '',
stripe_id: stripePlanCreated?.id ?? '',
paypal_id: paypalPlanCreated?.data?.id ?? '',
amount: stripePlanCreated?.unit_amount ? stripePlanCreated.unit_amount / 100 : params.amount,
interval: 4,
interval_count: 1,
trial_period_days: 0,
nickname: stripePlanCreated?.nickname ?? paypalPlanCreated.data.name,
service_id: localServiceId,
status: stripePlanCreated?.active == true ? 1 : params.status,
type: parseInt(planType),
};
let modelCreatedPlan = await paymentPlansModel.insert(planModelEntry);
if (!modelCreatedPlan) {
throw new Error('Internal error: error creating plan');
}
return modelCreatedPlan;
};
this.adminCreateTrialPlan = async function (planParams, planType, localServiceId) {
if (Object.keys(planParams.recurring).length === 0 || planParams.recurring.constructor !== Object) {
throw new Error('Must have a recurring parameter (subscription interval)');
}
if (!planParams.nickname) {
throw new Error('Must have a display name');
}
if (!localServiceId) {
throw new Error('Must provide system product id');
}
let priceCreated = await stripe.createPrice(planParams);
let planModelEntry = {
stripe_product_id: priceCreated.product,
stripe_id: priceCreated.id,
amount: priceCreated.unit_amount / 100,
interval: paymentPlansModel.inverse_interval_mapping(priceCreated.recurring.interval),
interval_count: priceCreated.recurring.interval_count,
trial_period_days: priceCreated.recurring.trial_period_days ?? 0,
nickname: priceCreated.nickname,
service_id: localServiceId,
status: priceCreated.active ? 1 : 0,
type: parseInt(planType),
};
let modelCreatedPlan = await paymentPlansModel.insert(planModelEntry);
if (!modelCreatedPlan) {
throw new Error('Internal error: error creating plan');
}
return modelCreatedPlan;
};
this.adminCancelSubscription = async function (subscriptionId) {
let subscriptionToCancel = await stripeSubscriptionsModel.getByPK(subscriptionId);
let subscriptionPlan = await paymentPlansModel.getByPK(subscriptionToCancel.plan_id);
if (!subscriptionToCancel || !subscriptionPlan) {
throw new Error('No subscription or plan of that id');
}
// let subscirptionType = subscriptionPlan.type;
let subscirptionType = 3;
//if normal subscription
switch (subscirptionType) {
case 0:
case 3:
case 4:
let cancelSubscriptionParams = {
subscriptionId: subscriptionToCancel.stripe_id,
params: {
prorate: this.prorate,
invoice_now: true,
},
};
let subscriptionCanceled = await stripe.cancelSubscription(cancelSubscriptionParams);
let localCancellationParams = {
status: stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status],
};
await stripeSubscriptionsModel.edit(localCancellationParams, subscriptionId);
return subscriptionCanceled;
default:
throw new Error('Something wrong');
}
};
this.userAddCard = async function (cardParams) {
let createdCard = await stripe.createCard(cardParams);
if (createdCard) {
if (createdCard.metadata.is_default == '1') {
let customerUpdateParams = {
customerId: createdCard.customer,
params: { default_source: createdCard.id },
};
await stripe.updateCustomer(customerUpdateParams);
let defaultCards = await stripeCardsModel.getAll({ is_default: 1 });
if (defaultCards.length > 0) {
defaultCards.forEach(async (card) => {
await stripeCardsModel.edit({ is_default: 0 }, card.id);
});
}
}
cardModelEntry = {
card_last: createdCard.last4 ?? '',
card_brand: createdCard.brand ?? '',
card_exp_month: createdCard.exp_month ?? '',
exp_year: createdCard.exp_year ?? '',
card_name: createdCard.metadata.card_name ?? '',
stripe_card_customer: createdCard.customer,
stripe_card_id: createdCard.id,
is_default: createdCard.metadata.is_default,
user_id: this.userId ?? null,
role_id: this.roleId ?? null,
};
var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
if (!modelCreatedCard) {
throw new Error('Internal error: Error adding card');
}
}
return modelCreatedCard;
};
this.changePlan = async function (currentSubscription, newPlan, user, card, couponId = '') {
//if current subscriptions is canceled
if (!currentSubscription || currentSubscription.status == 5) {
let subscriptionParams = {
customer: user.stripe_id,
items: { plan: newPlan.id },
coupon: couponId,
};
return await this.subscribe(subscriptionParams, newPlan);
}
//user has subscription (upgrade/ downgrade)
if (currentSubscription && currentSubscription.status != 5) {
if (currentSubscription.plan_id == newPlan.id) throw new Error('Same Plan');
}
};
}
+300
View File
@@ -0,0 +1,300 @@
require('dotenv').config();
const qs = require('qs'); //parse url form encoded params
const paypalBaseUrl = process.env.PAYPAL_BASE_URL;
const paypalSandboxBaseUrl = process.env.PAYPAL_SANDBOX_BASE_URL;
if (process.env.MODE === 'development') {
var axios = require('axios').create({
baseURL: paypalSandboxBaseUrl,
});
} else if (process.env.MODE === 'production') {
var axios = require('axios').create({
baseURL: paypalBaseUrl,
});
}
let accessToken = '';
module.exports = new Service();
function Service() {
// this.error = async function () {
// Error.call(this);
// };
this.setAccessToken = async function () {
accessToken = await this._getPaypalAccessToken().catch((error) => {
throw error;
});
return accessToken;
};
this._getPaypalAccessToken = async function () {
let data = await axios({
method: 'post',
url: '/v1/oauth2/token',
auth: {
username: process.env.PAYPAL_CLIENT_ID,
password: process.env.PAYPAL_SECRET,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: qs.stringify({
grant_type: 'client_credentials',
}),
}).catch((error) => {
throw error;
});
return data.data.access_token;
};
this.createProduct = async function (params) {
let requiredFields = ['name', 'type'];
requiredFields.forEach((field) => {
if (!params[field]) {
throw new Error(`Must have "${field}" parameter`);
}
});
params = this.filterParams(params);
let config = {
url: '/v1/catalogs/products',
data: params,
};
try {
return await this.axiosPost(config);
} catch (error) {
console.dir(error.response, { depth: null });
throw new Error(error.response?.data?.message ?? 'Internal Error: Error creating product');
}
};
this.getPlans = async function (paginationParams) {
let config = {
url: '/v1/billing/plans',
data: paginationParams,
};
try {
return await this.axiosGet(config);
} catch (error) {
console.error(error.response);
throw new Error('Internal Error: Error getting plans');
}
};
this.getProducts = async function (paginationParams) {
let config = {
url: '/v1/catalogs/products',
data: paginationParams,
};
try {
return await this.axiosGet(config);
} catch (error) {
console.error(error.response);
throw new Error('Internal Error: Error getting products');
}
};
this.getProductDetails = async function (productId) {
let config = {
url: `/v1/catalogs/products/${productId}`,
data: {},
};
try {
return await this.axiosGet(config);
} catch (error) {
console.error(error.response);
throw new Error('Internal Error: Error getting product details');
}
};
this.getSubscriptions = async function (paginationParams) {
let createdProduct = await axios({
method: 'get',
url: '/v1/billing/plans',
data: qs.stringify(paginationParams),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}).catch((error) => {
throw error;
});
return createdProduct.data;
};
this.createSubscription = async function (params) {
let config = {
url: '/v1/billing/subscriptions',
data: params,
};
return await this.axiosPost(config);
};
this.createPlan = async function (params) {
let config = {
url: '/v1/billing/plans',
data: params,
};
try {
return await this.axiosPost(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error creating paypal subscription plan');
}
};
this.deactivatePlan = async function (planId) {
let config = {
url: `/v1/billing/plans/${planId}/deactivate`,
data: {},
};
try {
return await this.axiosPost(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error deactivating paypal subscription plan');
}
};
this.getPlanDetails = async function (planId) {
let config = {
url: `/v1/billing/plans/${planId}`,
data: {},
};
try {
return await this.axiosGet(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error getting subscription details');
}
};
this.getSubscriptionDetails = async function (subscriptionId) {
let config = {
url: `/v1/billing/subscriptions/${subscriptionId}`,
data: {},
};
try {
return await this.axiosGet(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error getting subscription details');
}
};
this.updateProduct = async function (params, productId) {
let config = {
url: `/v1/catalogs/products/${productId}`,
data: params,
};
try {
return await this.axiosPatch(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error updating product');
}
};
this.updatePlan = async function (params, planId) {
let config = {
url: `/v1/billing/plans/${planId}`,
data: params,
};
try {
return await this.axiosPatch(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error updating plan');
}
};
this.updatePlanPricing = async function (params, planId) {
let config = {
url: `/v1/billing/plans/${planId}/update-pricing-schemes`,
data: params,
};
try {
return await this.axiosPost(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error updating plan');
}
};
this.activatePlan = async function (planId) {
let config = {
url: `/v1/billing/plans/${planId}/activate`,
data: {},
};
try {
return await this.axiosPost(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error updating plan');
}
};
this.deactivatePlan = async function (planId) {
let config = {
url: `/v1/billing/plans/${planId}/deactivate`,
data: {},
};
try {
return await this.axiosPost(config);
} catch (error) {
console.dir(error.response.data, { depth: null });
throw new Error('Internal Error: Error updating plan');
}
};
this.retrievePlan = async function (params, planId) {
let config = {
url: `/v1/billing/subscriptions/${planId}`,
data: params,
};
return await this.axiosGet(config);
};
this.axiosGet = async function (config) {
return await axios({
method: 'get',
url: config.url,
data: qs.stringify(config.data ?? {}),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
};
this.axiosPost = async function (config) {
return await axios({
method: 'post',
url: config.url,
data: JSON.stringify(config.data),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Prefer: 'return=representation',
Authorization: `Bearer ${accessToken}`,
},
});
};
this.axiosPatch = async function (config) {
return await axios({
method: 'patch',
url: config.url,
data: JSON.stringify(config.data),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
};
/**
* [filterParams filters paramters from null, undefined, empty strings, empty arrays and empty objects as it can cause unwanted changes]
* @param {object} params [object that can contain one more object inside]
* @return {object} [object with only truth variables]
*/
this.filterParams = function (params) {
Object.keys(params).forEach((param) => {
if (this.empty(params[param]) || params[param].length === 0) {
console.log(`Parameter empty, null or undefined`);
delete params[param];
} else if (params[param].constructor === Object && Object.entries(params[param]).length === 0) {
console.log(`Parameter object empty`);
delete params[param];
} else if (params[param].constructor === Object && Object.entries(params[param]).length > 0) {
this.filterParams(params[param]);
}
});
return params;
};
this.empty = (value) => value === null || value === undefined;
}
+705
View File
@@ -0,0 +1,705 @@
require('dotenv').config(); //require .env for stripe configurations
// const moment = require('moment'); //this is Payment Service
const paypal = require('./PaypalApi');
const db = require('../models');
const { update } = require('lodash');
const paypalPlansModel = db.paypal_plans;
const paypalServicesModel = db.paypal_services;
const paypalProductsModel = db.paypal_products;
module.exports = new Service();
function Service() {
this.userId = 0;
this.roleId = 0;
this.currency = process.env.PAYPAL_CURRENCY;
// this.prorate = process.env.STRIPE_PRORATE;
// this.forceCancel = process.env.STRIPE_FORCE_CANCEL;
this.setUserId = function (userId) {
this.userId = userId;
};
this.setRoleId = function (roleId) {
this.roleId = roleId;
};
this.setCurrency = function (currency) {
this.currency = currency;
};
// this.setProrationType = function (prorate) {
// this.prorate = prorate;
// };
// this.setCancelType = function (forceCancel) {
// this.forceCancel = forceCancel;
// };
// this.subscribe = async function (subscriptionParams, plan) {
// var subscription = await stripe.createSubscription(subscriptionParams);
// //create model entry
// let subscriptionModelEntry = {
// stripe_id: subscription.id ?? '',
// cancel_at_period_end: subscription.cancel_at_period_end ?? null,
// current_period_start: moment(new Date(subscription.current_period_start * 1000)).format('YYYY-MM-DD') ?? null,
// current_period_end: moment(new Date(subscription.current_period_end * 1000)).format('YYYY-MM-DD') ?? null,
// user_id: this.userId ?? null,
// role_id: this.roleId ?? null,
// plan_id: plan?.id ?? null,
// coupon_stripe_id: subscription.discount?.coupon?.id ?? '',
// customer_stripe_id: subscription.customer ?? '',
// collection_method: subscription.collection_method ?? '',
// interval: stripeSubscriptionsModel.inverse_interval_mapping(subscription.plan?.interval) ?? 0,
// interval_count: subscription.plan?.interval_count ?? '',
// trial_period_days: subscription.plan?.trial_period_days ?? 0,
// trial_end: subscription.trial_end ? moment(new Date(subscription.trial_end * 1000)).format('YYYY-MM-DD') : null,
// trial_start: subscription.trial_start ? moment(new Date(subscription.subscriptiontrial_start * 1000)).format('YYYY-MM-DD') : null,
// status: stripeSubscriptionsModel.inverse_status_mapping(subscription.status) ?? null,
// };
// let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
// if (!subscriptionCreatedId) {
// throw new Error('Subscription is not found.');
// }
// let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, subscription.status, plan);
// if (!subscriptionLogUpdated) {
// throw new Error('Error while editing subscription log');
// }
// return subscriptionCreatedId;
// };
// this.updateSubscriptionLog = async function (subscriptionCreatedId, subscriptionCreatedStatus, plan) {
// let subscriptionLog = await stripeSubscriptionsLogModel.getLast({ user_id: this.userId, role_id: this.roleId });
// if (!subscriptionLog) {
// let subscriptionLogModelEntry = {
// user_id: this.userId,
// role_id: this.roleId,
// plan_id: plan?.id ?? null,
// subscription_id: subscriptionCreatedId,
// type: plan?.type ?? null,
// status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
// };
// if (!(await stripeSubscriptionsLogModel.insert(subscriptionLogModelEntry))) {
// throw new Error('Subscription log add not successfull');
// }
// } else {
// let subscriptionLogModelEntry = {
// plan_id: plan?.id ?? null,
// subscription_id: subscriptionCreatedId,
// type: plan?.type ?? null,
// status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
// };
// if (!(await stripeSubscriptionsLogModel.edit(subscriptionLogModelEntry, subscriptionLog.id))) {
// throw new Error('Subscription log edit not successfull');
// }
// }
// return true;
// };
// this.createRegularSubscription = async function (user, plan, card, coupon) {
// let subscriptionParams = {
// customer: user.stripe_id,
// items: [{ price: plan.stripe_id }],
// trial_from_plan: true,
// default_payment_method: card.stripe_card_id,
// coupon: coupon?.stripe_id ?? null,
// };
// return await this.subscribe(subscriptionParams, plan);
// };
// this.createLifetimeSubscription = async function (user, plan, card, coupon) {
// //create invoice, finalize and pay it.
// let lifetimePlanStripeObject = await stripe.retrievePrice(plan.stripe_id);
// let invoiceItemParams = {
// customer: user.stripe_id,
// price: plan.stripe_id,
// discounts: [{ coupon: coupon?.stripe_id }],
// };
// let invoiceItemCreated = await stripe.createInvoiceItem(invoiceItemParams);
// let invoiceParams = {
// customer: user.stripe_id,
// default_payment_method: card?.stripe_card_id ?? null,
// };
// let invoiceCreated = await stripe.createInvoice(invoiceParams);
// let invoicePaid = await stripe.payInvoice(invoiceCreated.id);
// let subscriptionModelEntry = {
// stripe_id: invoiceItemCreated.id ?? '',
// cancel_at_period_end: null,
// current_period_start: moment().format('YYYY-MM-DD') ?? null,
// current_period_end: null,
// user_id: this.userId ?? null,
// role_id: this.roleId ?? null,
// plan_id: plan?.id ?? null,
// coupon_stripe_id: invoiceItemCreated.discounts.length > 0 ? coupon?.stripe_id : '',
// customer_stripe_id: invoicePaid.customer ?? '',
// collection_method: invoicePaid.collection_method ?? '',
// interval: 4,
// interval_count: null,
// trial_period_days: 0,
// trial_end: null,
// trial_start: null,
// status: 4,
// };
// let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
// if (!subscriptionCreatedId) {
// throw new Error('Subscription is not found.');
// }
// let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, 'active', plan);
// if (!subscriptionLogUpdated) {
// throw new Error('Error while editing subscription log');
// }
// return subscriptionCreatedId;
// };
// let calculateFullPeriod = function (interval, interval_count = 1) {
// let totalNumberOfDays = paypalPlansModel.interval_days_mapping()[interval] * interval_count;
// let cancelAtEpoch = moment().add(totalNumberOfDays, 'days').unix();
// return cancelAtEpoch;
// };
// this.createTrialSubscription = async function (user, plan, card, coupon) {
// let trialPlanFullPeriod = calculateFullPeriod(plan.interval, plan.interval_count);
// let subscriptionParams = {
// customer: user.stripe_id,
// items: [{ price: plan.stripe_id }],
// trial_from_plan: true,
// default_payment_method: card.stripe_card_id,
// coupon: coupon?.stripe_id ?? null,
// cancel_at: trialPlanFullPeriod,
// };
// return await this.subscribe(subscriptionParams, plan);
// };
// this.cancelRegularSubscription = async function (subscription) {
// if (this.forceCancel === 'true') {
// var cancellationParams = {
// subscriptionId: subscription.stripe_id,
// params: {
// invoice_now: true,
// prorate: this.prorate,
// },
// };
// let subscriptionCanceled = await stripe.cancelSubscription(cancellationParams);
// let newStatus = stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status];
// await stripeSubscriptionsModel.edit({ status: newStatus }, subscription.id);
// } else {
// let updateParams = {
// subscriptionId: subscription.stripe_id,
// params: {
// cancel_at_period_end: true,
// },
// };
// await stripe.updateSubscription(updateParams);
// await stripeSubscriptionsModel.edit({ cancel_at_period_end: 1 }, subscription.id);
// }
// };
// this.cancelLifetimeSubscription = async function (subscription) {
// //TODO check if we refund on this or not
// //we could make a fixed refund amount
// //right now we just cancel it locally and refund is never as it is a onetime product thing.
// let subscriptionEdited = await stripeSubscriptionsModel.edit({ status: 5 }, parseInt(subscription.id));
// if (!subscriptionEdited) {
// throw new Error("Couldn't edit subscription");
// }
// let subscriptionLog = await stripeSubscriptionsLogModel.getByFields({ subscription_id: parseInt(subscription.id), status: 1 });
// if (!subscriptionLog) {
// throw new Error("Couldn't find relative active subscription log");
// }
// let subscriptionLogEdited = await stripeSubscriptionsLogModel.edit({ status: 0 }, subscriptionLog.id);
// if (subscriptionLogEdited) {
// return true;
// }
// throw new Error("Couldn't Edit subscription log");
// };
// this.adminCreateProductPrice = async function (productStripeId, price, productLocalId) {
// let priceArgs = {
// unit_amount: convertToCents(price),
// currency: this.currency,
// product: productStripeId,
// };
// let priceCreated = await stripe.createPrice(priceArgs);
// let productModelEditEntry = {
// price,
// price_stripe_id: priceCreated.id,
// };
// let modelEditedProduct = await stripeProductsModel.edit(productModelEditEntry, productLocalId);
// if (!modelEditedProduct) {
// throw new Error('Internal Error: error setting price');
// }
// return priceCreated;
// };
this._createPaypalProduct = async function (params) {
let productParams = {
name: params.name,
description: params.description,
type: params.type,
category: params.category,
image_url: params.image,
home_url: params.url,
};
return await paypal.createProduct(productParams);
};
this.adminUpdateProduct = async function (category, paypalId) {
await paypal.setAccessToken();
let updateParams = [
{
op: 'replace',
path: '/category',
value: category,
},
];
await paypal.updateProduct(updateParams, paypalId);
return true;
};
this.adminCreateProduct = async function (params, type) {
if (!params.name) {
throw new Error('Must provide a name for your product');
}
if (!params.type) {
throw new Error('Must provide a type for your product');
}
await paypal.setAccessToken();
let createdPaypalProduct = await this._createPaypalProduct(params);
let paypalProductModelEntry = {
name: createdPaypalProduct.data.name,
paypal_id: createdPaypalProduct.data.id,
image: createdPaypalProduct.data.image_url ?? '',
type: createdPaypalProduct.data.type ?? '',
url: createdPaypalProduct.data.home_url ?? '',
category: createdPaypalProduct.data.category ?? '',
description: createdPaypalProduct.data.description ?? '',
status: params.status,
};
switch (params.type) {
case 'SERVICE': {
var modelCreatedProductId = await paypalServicesModel.insert(paypalProductModelEntry);
var product = await paypalServicesModel.getByPK(modelCreatedProductId);
break;
}
case 'PHYSICAL':
case 'DIGITAL': {
var modelCreatedProductId = await paypalProductsModel.insert(paypalProductModelEntry);
var product = await paypalProductsModel.getByPK(modelCreatedProductId);
break;
}
}
if (!modelCreatedProductId) {
throw new Error('Internal Error: Error adding product to our system');
}
return {
productPaypalId: product.paypal_id,
productLocalId: product.id,
};
};
/**
* [adminCreateRegularPlan allows an admin to create subscription plan through admin portal]
* @param {object} planParams [object that contain plan parameters]
* @param {integer} planType [integer that represent plan type in relative to stripe plans table] @see /models/stripe_plans.js type_mapping()
* @param {integer} localProductId [integer that represent product id in relative to stripe products table]
* @return {integer} [the id of the created plan in database]
*/
this.adminCreateRegularPlan = async function (params) {
if (!params.name) {
throw new Error('Must have a display name');
}
if (!params.type) {
throw new Error('Must provide plan type');
}
if (!params.service_id) {
throw new Error('Must attach a service');
}
await paypal.setAccessToken();
let service = await paypalServicesModel.getByPK(params.service_id);
let paypalSubscriptionPlanParams = {
product_id: service.paypal_id,
name: params.name,
billing_cycles: [
{
frequency: {
interval_unit: paypalPlansModel.interval_unit_mapping()[params.interval_unit].toUpperCase(),
interval_count: params.interval_count,
},
tenure_type: 'REGULAR',
sequence: 1,
total_cycles: 0,
pricing_scheme: {
fixed_price: {
value: params.pricing,
currency_code: this.currency.toUpperCase(),
},
},
},
],
payment_preferences: {
auto_bill_outstanding: true,
setup_fee_failure_action: 'CANCEL',
payment_failure_threshold: 0,
},
};
if (params.trial_period_days > 0) {
paypalSubscriptionPlanParams.billing_cycles[0].sequence = 2;
paypalSubscriptionPlanParams.billing_cycles.unshift({
frequency: {
interval_unit: 'DAY',
interval_count: parseInt(params.trial_period_days),
},
tenure_type: 'TRIAL',
sequence: 1,
total_cycles: 1,
});
}
let paypalPlanCreated = await paypal.createPlan(paypalSubscriptionPlanParams);
let hasTrial = paypalPlanCreated.data.billing_cycles[1] ? true : false;
let trialPlanInfo = hasTrial ? paypalPlanCreated.data.billing_cycles[0] : null;
let regularPlanInfo = hasTrial ? paypalPlanCreated.data.billing_cycles[1] : paypalPlanCreated.data.billing_cycles[0];
let price = regularPlanInfo.pricing_scheme.fixed_price.value;
let interval_unit = regularPlanInfo.frequency.interval_unit;
let interval_count = regularPlanInfo.frequency.interval_count;
let planModelEntry = {
name: paypalPlanCreated.data.name,
type: params.type,
paypal_id: paypalPlanCreated.data.id,
description: paypalPlanCreated.data.description,
paypal_product_id: paypalPlanCreated.data.product_id,
pricing: price,
interval_unit: this.getMappingKey(paypalPlansModel.interval_unit_mapping, interval_unit.toLowerCase()),
interval_count: interval_count,
service_id: params.service_id,
trial_period_days: trialPlanInfo ? trialPlanInfo.frequency.interval_count : 0,
status: this.getMappingKey(paypalPlansModel.status_mapping, paypalPlanCreated.data.status),
};
let modelCreatedPlan = await paypalPlansModel.insert(planModelEntry);
if (!modelCreatedPlan) {
throw new Error('Internal error: error creating plan');
}
return modelCreatedPlan;
};
this.updateRegularPlan = async function (params, currentSettings) {
//if plan has a trial
await paypal.setAccessToken();
let planPaypalId = currentSettings.paypal_id;
if (currentSettings.status == 0 && params.status == 1) {
await paypal.activatePlan(planPaypalId);
} else if (currentSettings.status == 1 && params.status == 0) {
await this._destroyPaypalPlan(planPaypalId);
return { status: 0 };
} else if (currentSettings.status == 0 && params.status == 0) {
throw new Error("Can't edit a plan if it is inactive");
}
let regularPlanIndex = currentSettings.trial_period_days ? 2 : 1;
let updateParams = [
{
op: 'replace',
path: '/name',
value: params.name,
},
];
await paypal.updatePlan(updateParams, planPaypalId);
updateParams = {
pricing_schemes: [
{
billing_cycle_sequence: regularPlanIndex,
pricing_scheme: {
fixed_price: {
value: params.pricing,
currency_code: this.currency.toUpperCase(),
},
},
},
],
};
await paypal.updatePlanPricing(updateParams, planPaypalId);
return { name: params.name, pricing: params.pricing, status: params.status };
};
this.adminCreateLifetimePlan = async function (params) {
//paypal doesn't allow lifetime plan
//how it works is i create a blan with a trial with 999 years and setup fee for subscription as the main amount for the lifetime plan
//paypal needs to have a regular plan so after the 999 years there is a regular plan with 0.01 amount money
//this works for both lifetime paid and free plans.
if (!params.name) {
throw new Error('Must have a display name');
}
if (!params.type) {
throw new Error('Must provide plan type');
}
if (!params.service_id) {
throw new Error('Must attach a service');
}
if (parseInt(params.interval_unit) !== 4) {
throw new Error('Interval must be "forever".');
}
let service = await paypalServicesModel.getByPK(params.service_id);
await paypal.setAccessToken();
let planParams = {
product_id: service.paypal_id,
name: params.name,
billing_cycles: [
{
frequency: {
interval_unit: 'YEAR',
interval_count: 1,
},
tenure_type: 'TRIAL',
sequence: 1,
total_cycles: 999,
},
{
frequency: {
interval_unit: 'YEAR',
interval_count: 1,
},
tenure_type: 'REGULAR',
sequence: 2,
total_cycles: 0,
pricing_scheme: {
fixed_price: {
value: '0.01',
currency_code: this.currency.toUpperCase(),
},
},
},
],
payment_preferences: {
auto_bill_outstanding: true,
setup_fee: {
value: params.pricing,
currency_code: this.currency.toUpperCase(),
},
setup_fee_failure_action: 'CANCEL',
payment_failure_threshold: 0,
},
};
let paypalPlanCreated = await paypal.createPlan(planParams);
let price = paypalPlanCreated.data.payment_preferences.setup_fee.value;
let planModelEntry = {
name: paypalPlanCreated.data.name,
type: params.type,
paypal_id: paypalPlanCreated.data.id,
description: paypalPlanCreated.data.description,
paypal_product_id: paypalPlanCreated.data.product_id,
pricing: price,
interval_unit: 4,
interval_count: null,
service_id: params.service_id,
trial_period_days: null,
status: this.getMappingKey(paypalPlansModel.status_mapping, paypalPlanCreated.data.status),
};
let modelCreatedPlan = await paypalPlansModel.insert(planModelEntry);
if (!modelCreatedPlan) {
throw new Error('Internal error: error creating plan');
}
return modelCreatedPlan;
};
this.adminCreateTrialPlan = async function (params) {
if (!params.name) {
throw new Error('Must have a display name');
}
if (!params.type) {
throw new Error('Must provide plan type');
}
if (!params.service_id) {
throw new Error('Must attach a service');
}
if (parseInt(params.interval_unit) === 4) {
throw new Error('Interval can\'t be "forever".');
}
let service = await paypalServicesModel.getByPK(params.service_id);
await paypal.setAccessToken();
let planParams = {
product_id: service.paypal_id,
name: params.name,
billing_cycles: [
// {
// frequency: {
// interval_unit: 'YEAR',
// interval_count: 1,
// },
// tenure_type: 'TRIAL',
// sequence: 1,
// total_cycles: 999,
// },
{
frequency: {
interval_unit: paypalPlansModel.interval_unit_mapping()[params.interval_unit].toUpperCase(),
interval_count: params.interval_count,
},
tenure_type: 'REGULAR',
sequence: 1,
total_cycles: 1,
pricing_scheme: {
fixed_price: {
value: params.pricing,
currency_code: this.currency.toUpperCase(),
},
},
},
],
payment_preferences: {
auto_bill_outstanding: true,
// setup_fee: {
// value: params.pricing,
// currency_code: this.currency.toUpperCase(),
// },
setup_fee_failure_action: 'CANCEL',
payment_failure_threshold: 0,
},
};
let paypalPlanCreated = await paypal.createPlan(planParams);
let planInfo = paypalPlanCreated.data.billing_cycles[0];
let price = planInfo.pricing_scheme.fixed_price.value;
let interval_unit = planInfo.frequency.interval_unit;
let interval_count = planInfo.frequency.interval_count;
let planModelEntry = {
name: paypalPlanCreated.data.name,
type: params.type,
paypal_id: paypalPlanCreated.data.id,
description: paypalPlanCreated.data.description,
paypal_product_id: paypalPlanCreated.data.product_id,
pricing: price,
interval_unit: this.getMappingKey(paypalPlansModel.interval_unit_mapping, interval_unit.toLowerCase()),
interval_count: interval_count,
service_id: params.service_id,
trial_period_days: 0,
status: this.getMappingKey(paypalPlansModel.status_mapping, paypalPlanCreated.data.status),
};
let modelCreatedPlan = await paypalPlansModel.insert(planModelEntry);
if (!modelCreatedPlan) {
throw new Error('Internal error: error creating plan');
}
return modelCreatedPlan;
};
this._destroyStripeLifetimePlan = async function (planStripeId) {
//stripe doesn't allow deleting a price. will be set to unactive instead
return await stripe.updatePrice({ priceId: planStripeId, params: { active: false } });
};
this._destroyPaypalPlan = async function (planPaypalId) {
//paypal doesn't allow deleting a price. will be set to unactive instead
return await paypal.deactivatePlan(planPaypalId);
};
// this.adminCancelSubscription = async function (subscriptionId) {
// let subscriptionToCancel = await stripeSubscriptionsModel.getByPK(subscriptionId);
// let subscriptionPlan = await paypalPlansModel.getByPK(subscriptionToCancel.plan_id);
// if (!subscriptionToCancel || !subscriptionPlan) {
// throw new Error('No subscription or plan of that id');
// }
// // let subscirptionType = subscriptionPlan.type;
// let subscirptionType = 3;
// //if normal subscription
// switch (subscirptionType) {
// case 0:
// case 3:
// case 4:
// let cancelSubscriptionParams = {
// subscriptionId: subscriptionToCancel.stripe_id,
// params: {
// prorate: this.prorate,
// invoice_now: true,
// },
// };
// let subscriptionCanceled = await stripe.cancelSubscription(cancelSubscriptionParams);
// let localCancellationParams = {
// status: stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status],
// };
// await stripeSubscriptionsModel.edit(localCancellationParams, subscriptionId);
// return subscriptionCanceled;
// default:
// throw new Error('Something wrong');
// }
// };
// this.userAddCard = async function (cardParams) {
// let createdCard = await stripe.createCard(cardParams);
// if (createdCard) {
// if (createdCard.metadata.is_default == '1') {
// let customerUpdateParams = {
// customerId: createdCard.customer,
// params: { default_source: createdCard.id },
// };
// await stripe.updateCustomer(customerUpdateParams);
// let defaultCards = await stripeCardsModel.getAll({ is_default: 1 });
// if (defaultCards.length > 0) {
// defaultCards.forEach(async (card) => {
// await stripeCardsModel.edit({ is_default: 0 }, card.id);
// });
// }
// }
// cardModelEntry = {
// card_last: createdCard.last4 ?? '',
// card_brand: createdCard.brand ?? '',
// card_exp_month: createdCard.exp_month ?? '',
// exp_year: createdCard.exp_year ?? '',
// card_name: createdCard.metadata.card_name ?? '',
// stripe_card_customer: createdCard.customer,
// stripe_card_id: createdCard.id,
// is_default: createdCard.metadata.is_default,
// user_id: this.userId ?? null,
// role_id: this.roleId ?? null,
// };
// var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
// if (!modelCreatedCard) {
// throw new Error('Internal error: Error adding card');
// }
// }
// return modelCreatedCard;
// };
// this.changePlan = async function (currentSubscription, newPlan, user, card, couponId = '') {
// //if current subscriptions is canceled
// if (!currentSubscription || currentSubscription.status == 5) {
// let subscriptionParams = {
// customer: user.stripe_id,
// items: { plan: newPlan.id },
// coupon: couponId,
// };
// return await this.subscribe(subscriptionParams, newPlan);
// }
// //user has subscription (upgrade/ downgrade)
// if (currentSubscription && currentSubscription.status != 5) {
// if (currentSubscription.plan_id == newPlan.id) throw new Error('Same Plan');
// }
// };
this.getMappingKey = function (mappingFunction, value) {
return Object.keys(mappingFunction()).find((key) => mappingFunction()[key] === value);
};
}
+17
View File
@@ -0,0 +1,17 @@
module.exports = {
verifyPermission: function (role, roleName) {
return function (req, res, next) {
const permissions = req.session.permissions || [];
const isAllowedPermission = permissions.find(
(permission) => `/${roleName}${permission}` === req.originalUrl,
);
if (isAllowedPermission) {
return next();
} else {
return res.redirect(`/${roleName}/dashboard`);
}
};
},
};
+4
View File
@@ -0,0 +1,4 @@
module.exports = function (req, res, next) {
res.setHeader("X-Powered-By", "Manaknightdigital Inc.");
next();
};
+52
View File
@@ -0,0 +1,52 @@
const db = require('../models');
module.exports = {
/**
* Get user profile from user and credential table
* @name profileService.get_profile
* @param {string} user_id user id
* @returns {Promise.<{email: string, password: string, first_name: string, last_name: string}>} profile fields
*/
async get_profile(user_id) {
try {
const { email, password } = await db.credential.getByField({ user_id });
const { first_name, last_name } = await db.user.getByPK(user_id);
return {
email,
password,
first_name,
last_name,
};
} catch (error) {
throw new Error(error.message);
}
},
/**
* Edit user profile update user and credential tables
* @param {string} user_id user id
* @param {{email: string, password: string, first_name: string, last_name: string}} profile user profile
* @returns {Promise.<{email: string, password: string, first_name: string, last_name: string}>} updated profile fields
*/
async edit_profile(user_id, profile) {
try {
await db.credential.editByField(
{ email: profile.email, password: profile.password },
{ user_id },
);
await db.user.edit(
{ first_name: profile.first_name, last_name: profile.last_name },
user_id,
);
return {
email,
password,
first_name,
last_name,
};
} catch (error) {
throw new Error(error.message);
}
},
};
+90
View File
@@ -0,0 +1,90 @@
const admin = require('firebase-admin');
class PushNotification {
/**
* Initialize firebase admin
* {@link https://firebase.google.com/docs/admin/setup#initialize-sdk Initialize the SDK}.
*/
constructor() {
this.admin = admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
this.messaging = this.admin.messaging();
}
/**
* Generate payload
* @param {{body: string, title: string, image_url: string}} payload
* @param {admin.messaging.MessagingPayload} override override default payload
*/
generatePayload(payload, override) {
return {
notification: {
body: payload.body,
title: payload.title,
},
data: payload,
apns: {
payload: {
aps: {
'mutable-content': 1,
},
},
fcm_options: {
image: payload.image_url,
},
},
android: {
notification: {
image: payload.image_url,
},
},
...override,
};
}
/**
* Generate options
* @param {admin.messaging.MessagingOptions} override override default options
*/
generateOptions(override) {
return {
contentAvailable: true,
priority: 'high',
timeToLive: 60 * 60 * 24,
...override,
};
}
/**
* Send push notification
* @param {string|string[]} deviceToken
* @param {admin.messaging.MessagingPayload} payload
* @param {admin.messaging.MessagingOptions} options
* @returns {Promise.<admin.messaging.MessagingDevicesResponse>}
* @example
* const pushNotification = new PushNotification();
* const payload = pushNotification.generatePayload({}) // {} = custom configuration
* const options = pushNotification.generateOptions({}); // {} = custom configuration
*
* pushNotification
* .sendPushNotification('device_token', payload, options)
* .then((response) => {
* console.log('message send successfully');
* })
* .catch((error) => {
* console.log('error occurred');
* });
*/
sendPushNotification(deviceToken, payload, options) {
return new Promise((resolve, reject) => {
this.messaging
.sendToDevice(deviceToken, payload, options)
.then((response) => resolve(response))
.catch((error) => reject(error));
});
}
}
module.exports = PushNotification;
+16
View File
@@ -0,0 +1,16 @@
const QRCode = require('qrcode');
module.exports = {
/**
* Generate QRCode
* @param {string} text QRCode string
*/
generateQRCode: async function (text) {
return new Promise((resolve, reject) => {
QRCode.toDataURL(text, (error, url) => {
if (error) reject(error);
else resolve(url);
});
});
},
};
+156
View File
@@ -0,0 +1,156 @@
require('dotenv').config();
const AWS = require('aws-sdk');
process.env.DYNAMIC_CONFIG_AWS_REGION = 'ap-south-1';
process.env.DYNAMIC_CONFIG_AWS_KEY = 'AKIA5RTSM74BBOL52JEA';
process.env.DYNAMIC_CONFIG_AWS_SECRET =
'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';
process.env.DYNAMIC_CONFIG_AWS_BUCKET = 'sentry-test90426-dev';
class S3Service {
/**
* Initialize S3 service
* @param {bucket} bucket override default aws bucket name
*/
constructor(bucket) {
AWS.config.update({
accessKeyId: process.env.DYNAMIC_CONFIG_AWS_KEY,
secretAccessKey: process.env.DYNAMIC_CONFIG_AWS_SECRET,
region: process.env.DYNAMIC_CONFIG_AWS_REGION,
signatureVersion: 'v4',
});
this.S3 = new AWS.S3();
this.bucket = bucket || process.env.DYNAMIC_CONFIG_AWS_BUCKET;
}
/**
* Upload an object to bucket
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property s3.upload}
* @param {string} key object key
* @param {any} body object body
* @param {AWS.S3.PutObjectRequest} options upload object params
* @returns {Promise.<AWS.S3.Types.ManagedUpload.SendData>}
* @throws {Error}
*/
upload(key, body, options) {
const params = {
Bucket: this.bucket,
Key: key,
Body: body,
...options,
};
return new Promise((resolve, reject) => {
this.S3.upload(params, function (error, data) {
if (error) reject(error);
else resolve(data);
});
});
}
/**
* Upload objects to bucket
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property s3.upload}
* @param {[{key: string, body: any}]} objects objects key and body
* @param {AWS.S3.PutObjectRequest} options upload objects params
* @returns {Promise.<[AWS.S3.Types.ManagedUpload.SendData]>}
* @throws {Error}
*/
batchUpload(objects, options) {
const uploads = [];
objects.forEach((object) => {
const singleUpload = this.upload(object.key, object.body, options);
uploads.push(singleUpload);
});
return Promise.all(uploads);
}
/**
* Get object from bucket
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property s3.getObject}
* @param {string} key object key
* @param {AWS.S3.GetObjectRequest} options get object params
* @returns {Promise.<AWS.S3.Types.GetObjectOutput>}
* @throws {AWS.S3.Error}
*/
get(key, options) {
const params = {
Bucket: this.bucket,
Key: key,
...options,
};
return new Promise((resolve, reject) => {
this.S3.getObject(params, function (error, data) {
if (error) reject(error);
else resolve(data);
});
});
}
/**
* Get objects from bucket
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property s3.getObject}
* @param {[key: string]} keys object keys
* @param {AWS.S3.GetObjectRequest} options get objects params
* @returns {Promise.<[AWS.S3.Types.GetObjectOutput]>}
* @throws {AWS.S3.Error}
*/
batchGet(keys, options) {
const gets = [];
keys.forEach((key) => {
const singleGet = this.get(key, options);
gets.push(singleGet);
});
return Promise.all(gets);
}
/**
* Delete object from bucket
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property s3.getObject}
* @param {string} key object key
* @param {AWS.S3.DeleteObjectRequest} options delete object params
* @returns {Promise.<AWS.S3.Types.DeleteObjectOutput>}
* @throws {AWS.S3.Error}
*/
delete(key, options) {
const params = {
Bucket: this.bucket,
Key: key,
...options,
};
return new Promise((resolve, reject) => {
this.S3.deleteObject(params, function (error, data) {
if (error) reject(error);
else resolve(data);
});
});
}
/**
* Delete objects from bucket
* {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObjects-property s3.getObjects}
* @param {[string]} key objects key
* @param {AWS.S3.DeleteObjectRequest} options delete objects params
* @returns {Promise.<[AWS.S3.Types.DeleteObjectOutput]>}
* @throws {AWS.S3.Error}
*/
batchDelete(keys, options) {
const deletes = [];
keys.forEach((key) => {
const singleDelete = this.delete(key, options);
deletes.push(singleDelete);
});
return Promise.all(deletes);
}
}
module.exports = S3Service;
+61
View File
@@ -0,0 +1,61 @@
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/**
* Session Service
* @copyright 2020 Manaknightdigital Inc.
* @link https://manaknightdigital.com
* @license Proprietary Software licensing
* @author Ryan Wong
*
*/
module.exports = {
verifySessionMiddleware: function (role, roleName = "") {
return function (req, res, next) {
const two_factor_authentication = req.session.two_factor_authentication;
const currentRole = req.session.role ? req.session.role : 0;
if (currentRole === 0 || currentRole != role) {
return res.redirect(`${roleName ? "/" + roleName : ""}/login?redirect_to=${req.originalUrl}`);
} else if (two_factor_authentication) {
return res.redirect(`/${roleName}/verify-account`);
} else {
next();
}
};
},
preventAuthRoutes: function (role, roleName) {
return (req, res, next) => {
const sessionRole = req.session.role;
if (sessionRole === role) {
return res.redirect("/" + roleName + "/dashboard");
} else {
next();
}
};
},
prevent2FA: function (role, roleName) {
return (req, res, next) => {
const currentRole = req.session.role;
const twoFA = req.session.two_factor_authentication;
if (currentRole !== role || !twoFA) {
return res.redirect("/" + roleName + "/login");
} else {
next();
}
};
},
randomString: function (len) {
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_+{}|":?><,./;[]';
let randomString = "";
for (let i = 0; i < len; i++) {
let randomPoz = Math.floor(Math.random() * charSet.length);
randomString += charSet.substring(randomPoz, randomPoz + 1);
}
return randomString;
},
};
+65
View File
@@ -0,0 +1,65 @@
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/**
* SMS Service
* @copyright 2020 Manaknightdigital Inc.
* @link https://manaknightdigital.com
* @license Proprietary Software licensing
* @author Ryan Wong
*
*/
const db = require('../models');
const accountSid = process.env.TWILIO_SID;
const authToken = process.env.TWILIO_TOKEN;
const phoneNumber = process.env.TWILIO_PHONE_NUMBER;
const client = require('twilio')(accountSid, authToken);
module.exports = {
from: phoneNumber,
template: function (slug) {
return new Promise(function (resolve, reject) {
db.sms
.findOne({ where: { slug } })
.then((response) => {
if (!response) {
return reject(`TEMPLATE_NOT_FOUND`);
} else resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
inject: function (template, payload) {
let body = template.content;
for (const key in payload) {
const element = payload[key];
body = body.replace(new RegExp('{{{' + key + '}}}', 'g'), element);
}
return body;
},
/**
* Send SMS
* @param {string} to
* @param {string} body
*/
send: function (to, body) {
let self = this;
return new Promise(function (resolve, reject) {
client.messages
.create({
body,
from: self.from,
to,
})
.then((message) => resolve(message.sid))
.catch((error) => reject(error));
});
},
};
+561
View File
@@ -0,0 +1,561 @@
const Stripe = require('stripe');
module.exports = new Service();
function Service() {
this.apiSecretKey = process.env.STRIPE_SECRET_KEY;
this.apiPublishKey = process.env.STRIPE_PUBLISH_KEY;
this.stripe = Stripe(this.apiSecretKey);
// Products Functions
this.createProduct = async function (params) {
args = this.filterParams(params);
let stripeType = 'product_create';
var result = await this.stripeType(stripeType, args);
return result;
};
this.retrieveProduct = async function (params) {
args = this.filterParams(params);
let stripeType = 'product_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
this.listAllProducts = async function (params) {
args = this.filterParams(params);
let stripeType = 'product_list_all';
var result = await this.stripeType(stripeType, args);
return result;
};
this.updateProduct = async function (params) {
args = this.filterParams(params);
let stripeType = 'product_update';
var result = await this.stripeType(stripeType, args);
return result;
};
// Cards Functions
this.createCard = async function (params) {
args = this.filterParams(params);
let stripeType = 'card_create';
var result = await this.stripeType(stripeType, args);
return result;
};
// Customers Functions
this.createCustomer = async function (params) {
args = this.filterParams(params);
let stripeType = 'customer_create';
var result = await this.stripeType(stripeType, args);
return result;
};
this.updateCustomer = async function (params) {
args = this.filterParams(params);
let stripeType = 'customer_update';
var result = await this.stripeType(stripeType, args);
return result;
};
this.retrieveCustomer = async function (params) {
args = this.filterParams(params);
let stripeType = 'customer_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
this.listAllCustomers = async function (params) {
args = this.filterParams(params);
let stripeType = 'customer_list_all';
var result = await this.stripeType(stripeType, args);
return result;
};
// Card Functions
this.retrieveCard = async function (params) {
args = this.filterParams(params);
let stripeType = 'card_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
// Prices Functions
this.createPrice = async function (params) {
args = this.filterParams(params);
let stripeType = 'price_create';
var result = await this.stripeType(stripeType, args);
return result;
};
this.updatePrice = async function (params) {
args = this.filterParams(params);
let stripeType = 'price_update';
var result = await this.stripeType(stripeType, args);
return result;
};
this.retrievePrice = async function (params) {
args = this.filterParams(params);
let stripeType = 'price_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
this.listAllPrices = async function (params) {
args = this.filterParams(params);
let stripeType = 'price_list_all';
var result = await this.stripeType(stripeType, args);
return result;
};
// Charges Functions
this.createCharge = async function (params) {
args = this.filterParams(params);
let stripeType = 'charge_create';
var result = await this.stripeType(stripeType, args);
return result;
};
this.retrieveCharge = async function (params) {
// args = params;
args = this.filterParams(params);
let stripeType = 'charge_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
//Invoices Functions
this.createInvoice = async function (params) {
args = this.filterParams(params);
let stripeType = 'invoice_create';
var result = await this.stripeType(stripeType, args);
return result;
};
this.payInvoice = async function (params) {
args = this.filterParams(params);
let stripeType = 'invoice_pay';
var result = await this.stripeType(stripeType, args);
return result;
};
this.retrieveInvoice = async function (params) {
args = this.filterParams(params);
let stripeType = 'invoice_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
this.createInvoiceItem = async function (params) {
args = this.filterParams(params);
let stripeType = 'invoice_item_create';
var result = await this.stripeType(stripeType, args);
return result;
};
//subscriptions
this.createSubscription = async function (params) {
args = this.filterParams(params);
let stripeType = 'subscription_create';
var result = await this.stripeType(stripeType, args);
return result;
};
this.updateSubscription = async function (params) {
args = this.filterParams(params);
let stripeType = 'subscription_update';
var result = await this.stripeType(stripeType, args);
return result;
};
this.retrieveSubscription = async function (params) {
args = this.filterParams(params);
let stripeType = 'subscription_retrieve';
var result = await this.stripeType(stripeType, args);
return result;
};
this.cancelSubscription = async function (params) {
args = this.filterParams(params);
let stripeType = 'subscription_cancel';
var result = await this.stripeType(stripeType, args);
return result;
};
this.listAllSubscriptions = async function (params) {
args = this.filterParams(params);
let stripeType = 'subscription_list_all';
var result = await this.stripeType(stripeType, args);
return result;
};
this.stripeType = async function (type, args) {
try {
switch (type) {
//Products
case 'product_create':
// @see https://stripe.com/docs/api/products/create?lang=node
var result = await this.stripe.products.create(args);
break;
case 'product_delete':
// @see https://stripe.com/docs/api/products/delete?lang=node
var result = await this.stripe.products.del(args);
break;
case 'product_retrieve':
// @see https://stripe.com/docs/api/products/retrieve?lang=node
var result = await this.stripe.products.retrieve(args);
break;
case 'product_update':
// @see https://stripe.com/docs/api/products/update?lang=node
var result = await this.stripe.products.update(args.productId, args.params);
break;
case 'product_list_all':
// @see https://stripe.com/docs/api/products/list?lang=node
var result = await this.stripe.products.list(args);
break;
//Prices
case 'price_create':
// @see https://stripe.com/docs/api/prices/create?lang=node
var result = await this.stripe.prices.create(args);
break;
case 'price_retrieve':
// @see https://stripe.com/docs/api/prices/retrieve?lang=node
var result = await this.stripe.prices.retrieve(args);
break;
case 'price_update':
// @see https://stripe.com/docs/api/prices/update?lang=node
var result = await this.stripe.prices.update(args.priceId, args.params);
break;
case 'price_list_all':
// @see https://stripe.com/docs/api/prices/list?lang=node
var result = await this.stripe.prices.list(args);
break;
//Charges
case 'charge_create':
// @see https://stripe.com/docs/api/charges/create?lang=node
var result = await this.stripe.charges.create(args);
break;
case 'charge_retrieve':
// @see https://stripe.com/docs/api/charges/retrieve?lang=node
var result = await this.stripe.charges.retrieve(args);
break;
case 'charge_update':
// @see https://stripe.com/docs/api/charges/update?lang=node
var result = await this.stripe.charges.update(args.chargeId, args.params);
break;
case 'charge_capture':
// @see https://stripe.com/docs/api/charges/capture?lang=node
var result = await this.stripe.charges.capture(args);
break;
case 'charge_list_all':
// @see https://stripe.com/docs/api/charges/list?lang=node
var result = await this.stripe.charges.list(args);
break;
//Customer
case 'customer_create':
// @see https://stripe.com/docs/api/customers/create?lang=node
var result = await this.stripe.customers.create(args);
break;
case 'customer_retrieve':
// @see https://stripe.com/docs/api/customers/retrieve?lang=node
var result = await this.stripe.customers.retrieve(args);
break;
case 'customer_update':
// @see https://stripe.com/docs/api/customers/update?lang=node
var result = await this.stripe.customers.update(args.customerId, args.params);
break;
case 'customer_delete':
// @see https://stripe.com/docs/api/customers/capture?lang=node
var result = await this.stripe.customers.del(args);
break;
case 'customer_list_all':
// @see https://stripe.com/docs/api/customers/list?lang=node
var result = await this.stripe.customers.list(args);
break;
//Refunds
case 'refund_create':
// @see https://stripe.com/docs/api/refunds/create?lang=node
var result = await this.stripe.refunds.create(args);
break;
case 'refund_retrieve':
// @see https://stripe.com/docs/api/refunds/retrieve?lang=node
var result = await this.stripe.refunds.retrieve(args);
break;
case 'refund_update':
// @see https://stripe.com/docs/api/refunds/update?lang=node
var result = await this.stripe.refunds.update(args.refundId, args.params);
break;
case 'refund_list_all':
// @see https://stripe.com/docs/api/refunds/list?lang=node
var result = await this.stripe.refunds.list(args);
break;
//Cards (Sources)
case 'card_create':
// @see https://stripe.com/docs/api/cards/create?lang=node
var result = await this.stripe.customers.createSource(args.customerId, args.params);
break;
case 'card_retrieve':
// @see https://stripe.com/docs/api/cards/retrieve?lang=node
var result = await this.stripe.customers.retrieveSource(args.customerId, args.cardId);
break;
case 'card_update':
// @see https://stripe.com/docs/api/cards/update?lang=node
var result = await this.stripe.customers.updateSource(args.customerId, args.cardId, args.params);
break;
case 'card_delete':
// @see https://stripe.com/docs/api/cards/delete?lang=node
var result = await this.stripe.customers.deleteSource(args.customerId, args.cardId);
break;
case 'card_list_all':
// @see https://stripe.com/docs/api/cards/list?lang=node
var result = await this.stripe.customers.listSources(args.customerId, args.params);
break;
//Payment Method
case 'paymentMethod_create':
// @see https://stripe.com/docs/api/payment_methods/create?lang=node
var result = await this.stripe.paymentMethods.create(args);
break;
case 'paymentMethod_retrieve':
// @see https://stripe.com/docs/api/payment_methods/retrieve?lang=node
var result = await this.stripe.paymentMethods.retrieve(args);
break;
case 'paymentMethod_update':
// @see https://stripe.com/docs/api/payment_methods/update?lang=node
var result = await this.stripe.paymentMethods.update(args.paymentMethodId, args.params);
break;
case 'paymentMethod_attach':
// @see https://stripe.com/docs/api/payment_methods/attach?lang=node
var result = await this.stripe.paymentMethods.attach(args.paymentMethodId, args.params);
break;
case 'paymentMethod_detach':
// @see https://stripe.com/docs/api/payment_methods/attach?lang=node
var result = await this.stripe.paymentMethods.attach(args);
break;
case 'paymentMethod_list_all':
// @see https://stripe.com/docs/api/payment_methods/list?lang=node
var result = await this.stripe.paymentMethods.list(args);
break;
//Session
case 'session_create':
// @see https://stripe.com/docs/api/sessions/create?lang=node
var result = await this.stripe.checkout.sessions.create(args);
break;
case 'session_retrieve':
// @see https://stripe.com/docs/api/sessions/retrieve?lang=node
var result = await this.stripe.checkout.sessions.retrieve(args);
break;
case 'session_list_all_lineItems':
// @see https://stripe.com/docs/api/sessions/line_items?lang=node
var result = function () {
return new Promise((resolve, reject) => {
this.stripe.checkout.sessions.listLineItems(args.sessionId, args.params, function (err, lineItems) {
if (err) return reject(err);
return resolve(lineItems);
});
});
};
result = await result();
break;
case 'session_list_all':
// @see https://stripe.com/docs/api/session/list?lang=node
var result = await this.stripe.checkout.sessions.list(args);
break;
//Coupon
case 'coupon_create':
// @see https://stripe.com/docs/api/coupons/create?lang=node
var result = await this.stripe.coupons.create(args);
break;
case 'coupon_retrieve':
// @see https://stripe.com/docs/api/coupons/retrieve?lang=node
var result = await this.stripe.coupons.retrieve(args);
break;
case 'coupon_update':
// @see https://stripe.com/docs/api/coupons/update?lang=node
var result = await this.stripe.coupons.update(args.couponId, args.params);
break;
case 'coupon_delete':
// @see https://stripe.com/docs/api/coupons/delete?lang=node
var result = await this.stripe.coupons.del(args);
break;
case 'coupon_list_all':
// @see https://stripe.com/docs/api/coupons/list?lang=node
var result = await this.stripe.coupons.list(args);
break;
//Invoice items
case 'invoice_item_create':
// @see https://stripe.com/docs/api/invoiceitems/create?lang=node
var result = await this.stripe.invoiceItems.create(args);
break;
//Invoice
case 'invoice_create':
// @see https://stripe.com/docs/api/invoices/create?lang=node
var result = await this.stripe.invoices.create(args);
break;
case 'invoice_retrieve':
// @see https://stripe.com/docs/api/invoices/retrieve?lang=node
var result = await this.stripe.invoices.retrieve(args);
break;
case 'invoice_update':
// @see https://stripe.com/docs/api/invoices/update?lang=node
var result = await this.stripe.invoices.update(args.invoiceId, args.params);
break;
case 'invoice_delete':
// @see https://stripe.com/docs/api/invoices/delete?lang=node
var result = await this.stripe.invoices.del(args);
break;
case 'invoice_finalize':
// @see https://stripe.com/docs/api/invoices/finalize?lang=node
var result = await this.stripe.invoices.finalizeInvoice(args);
break;
case 'invoice_pay':
// @see https://stripe.com/docs/api/invoices/pay?lang=node
var result = await this.stripe.invoices.pay(args);
break;
case 'invoice_void':
// @see https://stripe.com/docs/api/invoices/void?lang=node
var result = await this.stripe.invoices.voidInvoice(args);
break;
case 'invoice_list_all':
// @see https://stripe.com/docs/api/invoices/list?lang=node
var result = await this.stripe.invoices.list(args);
break;
//Subscription
case 'subscription_create':
// @see https://stripe.com/docs/api/subscriptions/create?lang=node
var result = await this.stripe.subscriptions.create(args);
break;
case 'subscription_retrieve':
// @see https://stripe.com/docs/api/subscriptions/retrieve?lang=node
var result = await this.stripe.subscriptions.retrieve(args);
break;
case 'subscription_update':
// @see https://stripe.com/docs/api/subscriptions/update?lang=node
var result = await this.stripe.subscriptions.update(args.subscriptionId, args.params);
break;
case 'subscription_cancel':
// @see https://stripe.com/docs/api/subscriptions/cancel?lang=node
var result = await this.stripe.subscriptions.del(args.subscriptionId, args.params);
break;
case 'subscription_list_all':
// @see https://stripe.com/docs/api/subscriptions/list?lang=node
var result = await this.stripe.subscriptions.list(args);
break;
//Subscription schedule
case 'subscription_schedule_create':
// @see https://stripe.com/docs/api/subscription_schedules/create?lang=node
var result = await this.stripe.subscriptionSchedules.create(args);
break;
case 'subscription_schedule_retrieve':
// @see https://stripe.com/docs/api/subscription_schedules/retrieve?lang=node
var result = await this.stripe.subscriptionSchedules.retrieve(args);
break;
case 'subscription_schedule_update':
// @see https://stripe.com/docs/api/subscription_schedules/update?lang=node
var result = await this.stripe.subscriptionSchedules.update(args.subScheduleId, args.params);
break;
case 'subscription_schedule_cancel':
// @see https://stripe.com/docs/api/subscription_schedules/cancel?lang=node
var result = await this.stripe.subscriptionSchedules.cancel(args);
break;
case 'subscription_schedule_release':
// @see https://stripe.com/docs/api/subscription_schedules/release?lang=node
var result = await this.stripe.subscriptionSchedules.release(args);
break;
case 'subscription_schedule_list_all':
// @see https://stripe.com/docs/api/subscription_schedules/list?lang=node
var result = await this.stripe.subscriptionSchedules.list(args);
break;
//Transaction
case 'transaction_retrieve':
// @see https://stripe.com/docs/api/issuing/transactions/retrieve?lang=node
var result = await this.stripe.issuing.transactions.retrieve(args);
break;
case 'transaction_update':
// @see https://stripe.com/docs/api/issuing/transactions/update?lang=node
var result = await this.stripe.issuing.transactions.update(args.transactionId, args.params);
break;
case 'transaction_list_all':
// @see https://stripe.com/docs/api/issuing/transactions/list?lang=node
var result = await this.stripe.issuing.transactions.list(args);
break;
//Order
case 'order_create':
// @see https://stripe.com/docs/api/orders/create?lang=node
var result = await this.stripe.orders.create(args);
break;
case 'order_retrieve':
// @see https://stripe.com/docs/api/orders/retrieve?lang=node
var result = await this.stripe.orders.retrieve(args);
break;
case 'order_update':
// @see https://stripe.com/docs/api/orders/update?lang=node
var result = await this.stripe.orders.update(args.orderId, args.params);
break;
case 'order_pay':
// @see https://stripe.com/docs/api/orders/pay?lang=node
var result = await this.stripe.orders.pay(args.orderId, args.params);
break;
case 'order_list_all':
// @see https://stripe.com/docs/api/orders/list?lang=node
var result = await this.stripe.orders.list(args);
break;
case 'order_return':
// @see https://stripe.com/docs/api/orders/return?lang=node
var result = function () {
return new Promise((resolve, reject) => {
this.stripe.orders.returnOrder(args.orderId, args.params, function (err, order) {
if (err) return reject(err);
return resolve(order);
});
});
};
result = await result();
break;
//Webhooks
case 'webhook_create':
// @see https://stripe.com/docs/api/webhook_endpoints/create?lang=node
var result = await this.stripe.webhookEndpoints.create(args);
break;
case 'webhook_retrieve':
// @see https://stripe.com/docs/api/webhook_endpoints/retrieve?lang=node
var result = await this.stripe.webhookEndpoints.retrieve(args);
break;
case 'webhook_update':
// @see https://stripe.com/docs/api/webhook_endpoints/update?lang=node
var result = await this.stripe.webhookEndpoints.update(args.webhookId, args.params);
break;
case 'webhook_delete':
// @see https://stripe.com/docs/api/webhook_endpoints/delete?lang=node
var result = await this.stripe.webhookEndpoints.del(args);
break;
case 'webhook_list_all':
// @see https://stripe.com/docs/api/webhook_endpoints/list?lang=node
var result = await this.stripe.webhookEndpoints.list(args);
break;
}
return result;
} catch (error) {
// return { error: true, message: error.message, statusCode: error.statusCode, errorObject: error.raw };
console.dir(error, { depth: null });
throw new Error('Internal Error: error when doing stripe action');
}
};
/**
* [filterParams filters paramters from null, undefined, empty strings, empty arrays and empty objects as it can cause unwanted changes]
* @param {object} params [object that can contain one more object inside]
* @return {object} [object with only truth variables]
*/
this.filterParams = function (params) {
Object.keys(params).forEach((param) => {
if (this.empty(params[param]) || (Array.isArray(params[param]) && !params[param].length)) {
console.log(`Parameter empty, null or undefined`);
delete params[param];
} else if (params[param].constructor === Object && Object.entries(params[param]).length === 0) {
console.log(`Parameter object empty`);
delete params[param];
} else if (params[param].constructor === Object && Object.entries(params[param]).length > 0) {
this.filterParams(params[param]);
}
});
return params;
};
// this.empty = (value) => value === null || value === undefined || value === '';
this.empty = (value) => value === null || value === undefined;
}
File diff suppressed because it is too large Load Diff
+62
View File
@@ -0,0 +1,62 @@
const path = require('path');
const aws = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');
const { createDirectoriesRecursiveV2 } = require('./../core/helpers');
module.exports = {
upload: function (location) {
if (process.env.NODE_ENV === 'production') {
return this.s3_upload(location);
} else {
return this.local_upload('');
}
},
s3_upload: function (location) {
try {
aws.config.update({
accessKeyId: process.env.DYNAMIC_CONFIG_AWS_KEY,
secretAccessKey: process.env.DYNAMIC_CONFIG_AWS_SECRET,
region: process.env.DYNAMIC_CONFIG_AWS_REGION,
});
const s3 = new aws.S3();
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.DYNAMIC_CONFIG_AWS_BUCKET,
acl: 'public-read',
contentType: multerS3.AUTO_CONTENT_TYPE,
key: function (req, file, cb) {
cb(null, location + file.originalname);
},
}),
});
return upload;
} catch (error) {
console.log('s3_upload => ', error);
}
},
local_upload: function () {
try {
const storage = multer.diskStorage({
destination: function (req, file, cb) {
createDirectoriesRecursiveV2(path.join(__dirname, '..', 'public', 'uploads'));
cb(null, path.join(__dirname, '..', 'public', 'uploads'));
},
filename: function (req, file, cb) {
const fileName = file.filename ?? file.originalname;
cb(null, Date.now() + '-' + fileName);
},
});
const upload = multer({ storage: storage });
return upload;
} catch (error) {
console.log('local_upload => ', error);
}
},
};
+85
View File
@@ -0,0 +1,85 @@
const { Validator, addCustomMessages } = require('node-input-validator');
// {fieldName:message} eg:{email:"Invalid Email", password:"Password too short"}
const formatValidationError = (error) => {
const formatted = Object.entries(error)
.map(([key, value]) => ({
field: key,
message: value.message,
}))
.reduce((accumulator, currentValue) => {
if (!accumulator[currentValue]) {
accumulator[currentValue.field] = currentValue.message;
}
return accumulator;
}, {});
return formatted;
};
module.exports = {
/**
* Input Validator middleware for controller
* @param {object} validationObject object defining body fields and its validation types eg:{email:required|email}
* @param {object} _extendMessages object defining message to throw on validation error eg: {"email.required":"Email is required","email.email":"Invalid email"}
*
*/
validateInput: (validationObject = {}, _extendMessages = {}) => async (
req,
res,
next,
) => {
const validation = new Validator(req.body, validationObject);
addCustomMessages(_extendMessages);
try {
const isValid = await validation.check();
if (!isValid) {
req.validationError = formatValidationError(validation.errors);
}
return next();
} catch (error) {
req.validationError = error.message;
return next();
}
},
handleValidationErrorForViews: (
req,
res,
viewModel,
viewPath = '/',
fieldsStoreKey,
defaultValue = {},
) => {
const validationError = req.validationError;
if (validationError) {
// Remembers fields if validation error occurs
Object.entries(defaultValue).forEach(([key, value]) => {
viewModel[fieldsStoreKey][key] = value;
});
if (typeof validationError === 'string') {
viewModel.error = validationError;
} else {
viewModel.validationError = req.validationError;
}
return res.render(viewPath, viewModel);
}
},
handleValidationErrorForAPI: (req, res, next) => {
const validationError = req.validationError;
if (validationError) {
let error;
if (typeof validationError === 'string') {
error = validationError;
} else {
error = req.validationError;
}
return res.json({ success: false, error });
}
next();
},
};
@@ -0,0 +1,449 @@
{
"categories": {
"ACADEMIC_SOFTWARE": "Academic Software",
"ACCESSORIES": "Accessories",
"ACCOUNTING": "Accounting",
"ADULT": "Adult",
"ADVERTISING": "Advertising",
"AFFILIATED_AUTO_RENTAL": "Affiliated Auto Rental",
"AGENCIES": "Agencies",
"AGGREGATORS": "Aggregators",
"AGRICULTURAL_COOPERATIVE_FOR_MAIL_ORDER": "Agricultural Cooperative for Mail Order",
"AIR_CARRIERS_AIRLINES": "Air Carriers, Airlines",
"AIRLINES": "Airlines",
"AIRPORTS_FLYING_FIELDS": "Airports, Flying Fields",
"ALCOHOLIC_BEVERAGES": "Alcoholic Beverages",
"AMUSEMENT_PARKS_CARNIVALS": "Amusement Parks/Carnivals",
"ANIMATION": "Animation",
"ANTIQUES": "Antiques",
"APPLIANCES": "Appliances",
"AQUARIAMS_SEAQUARIUMS_DOLPHINARIUMS": "Aquariams Seaquariums Dolphinariums",
"ARCHITECTURAL_ENGINEERING_AND_SURVEYING_SERVICES": "Architectural,Engineering,And Surveying Services",
"ART_AND_CRAFT_SUPPLIES": "Art & Craft Supplies",
"ART_DEALERS_AND_GALLERIES": "Art dealers and galleries",
"ARTIFACTS_GRAVE_RELATED_AND_NATIVE_AMERICAN_CRAFTS": "Artifacts, Grave related, and Native American Crafts",
"ARTS_AND_CRAFTS": "Arts and crafts",
"ARTS_CRAFTS_AND_COLLECTIBLES": "Arts, crafts, and collectibles",
"AUDIO_BOOKS": "Audio books",
"AUTO_ASSOCIATIONS_CLUBS": "Auto Associations/Clubs",
"AUTO_DEALER_USED_ONLY": "Auto dealer - used only",
"AUTO_RENTALS": "Auto Rentals",
"AUTO_SERVICE": "Auto service",
"AUTOMATED_FUEL_DISPENSERS": "Automated Fuel Dispensers",
"AUTOMOBILE_ASSOCIATIONS": "Automobile Associations",
"AUTOMOTIVE": "Automotive",
"AUTOMOTIVE_REPAIR_SHOPS_NON_DEALER": "Automotive Repair Shops - Non-Dealer",
"AUTOMOTIVE_TOP_AND_BODY_SHOPS": "Automotive Top And Body Shops",
"AVIATION": "Aviation",
"BABIES_CLOTHING_AND_SUPPLIES": "Babies Clothing & Supplies",
"BABY": "Baby",
"BANDS_ORCHESTRAS_ENTERTAINERS": "Bands,Orchestras,Entertainers",
"BARBIES": "Barbies",
"BATH_AND_BODY": "Bath and body",
"BATTERIES": "Batteries",
"BEAN_BABIES": "Bean Babies",
"BEAUTY": "Beauty",
"BEAUTY_AND_FRAGRANCES": "Beauty and fragrances",
"BED_AND_BATH": "Bed & Bath",
"BICYCLE_SHOPS_SALES_AND_SERVICE": "Bicycle Shops-Sales And Service",
"BICYCLES_AND_ACCESSORIES": "Bicycles & Accessories",
"BILLIARD_POOL_ESTABLISHMENTS": "Billiard/Pool Establishments",
"BOAT_DEALERS": "Boat Dealers",
"BOAT_RENTALS_AND_LEASING": "Boat Rentals And Leasing",
"BOATING_SAILING_AND_ACCESSORIES": "Boating, sailing and accessories",
"BOOKS": "Books",
"BOOKS_AND_MAGAZINES": "Books and magazines",
"BOOKS_MANUSCRIPTS": "Books, Manuscripts",
"BOOKS_PERIODICALS_AND_NEWSPAPERS": "Books, Periodicals And Newspapers",
"BOWLING_ALLEYS": "Bowling Alleys",
"BULLETIN_BOARD": "Bulletin board",
"BUS_LINE": "Bus line",
"BUS_LINES_CHARTERS_TOUR_BUSES": "Bus Lines,Charters,Tour Buses",
"BUSINESS": "Business",
"BUSINESS_AND_SECRETARIAL_SCHOOLS": "Business and secretarial schools",
"BUYING_AND_SHOPPING_SERVICES_AND_CLUBS": "Buying And Shopping Services And Clubs",
"CABLE_SATELLITE_AND_OTHER_PAY_TELEVISION_AND_RADIO_SERVICES": "Cable,Satellite,And Other Pay Television And Radio Services",
"CABLE_SATELLITE_AND_OTHER_PAY_TV_AND_RADIO": "Cable, satellite, and other pay TV and radio",
"CAMERA_AND_PHOTOGRAPHIC_SUPPLIES": "Camera and photographic supplies",
"CAMERAS": "Cameras",
"CAMERAS_AND_PHOTOGRAPHY": "Cameras & Photography",
"CAMPER_RECREATIONAL_AND_UTILITY_TRAILER_DEALERS": "Camper,Recreational And Utility Trailer Dealers",
"CAMPING_AND_OUTDOORS": "Camping and outdoors",
"CAMPING_AND_SURVIVAL": "Camping & Survival",
"CAR_AND_TRUCK_DEALERS": "Car And Truck Dealers",
"CAR_AND_TRUCK_DEALERS_USED_ONLY": "Car And Truck Dealers - Used Only",
"CAR_AUDIO_AND_ELECTRONICS": "Car Audio & Electronics",
"CAR_RENTAL_AGENCY": "Car rental agency",
"CATALOG_MERCHANT": "Catalog Merchant",
"CATALOG_RETAIL_MERCHANT": "Catalog/Retail Merchant",
"CATERING_SERVICES": "Catering services",
"CHARITY": "Charity",
"CHECK_CASHIER": "Check Cashier",
"CHILD_CARE_SERVICES": "Child Care Services",
"CHILDREN_BOOKS": "Children Books",
"CHIROPODISTS_PODIATRISTS": "Chiropodists/Podiatrists",
"CHIROPRACTORS": "Chiropractors",
"CIGAR_STORES_AND_STANDS": "Cigar Stores And Stands",
"CIVIC_SOCIAL_FRATERNAL_ASSOCIATIONS": "Civic, Social, Fraternal Associations",
"CIVIL_SOCIAL_FRAT_ASSOCIATIONS": "Civil/Social/Frat Associations",
"CLOTHING": "Clothing",
"CLOTHING_ACCESSORIES_AND_SHOES": "Clothing, accessories, and shoes",
"CLOTHING_RENTAL": "Clothing Rental",
"COFFEE_AND_TEA": "Coffee and tea",
"COIN_OPERATED_BANKS_AND_CASINOS": "Coin Operated Banks & Casinos",
"COLLECTIBLES": "Collectibles",
"COLLECTION_AGENCY": "Collection agency",
"COLLEGES_AND_UNIVERSITIES": "Colleges and universities",
"COMMERCIAL_EQUIPMENT": "Commercial Equipment",
"COMMERCIAL_FOOTWEAR": "Commercial Footwear",
"COMMERCIAL_PHOTOGRAPHY": "Commercial photography",
"COMMERCIAL_PHOTOGRAPHY_ART_AND_GRAPHICS": "Commercial photography, art, and graphics",
"COMMERCIAL_SPORTS_PROFESSIONA": "Commercial Sports/Professiona",
"COMMODITIES_AND_FUTURES_EXCHANGE": "Commodities and futures exchange",
"COMPUTER_AND_DATA_PROCESSING_SERVICES": "Computer and data processing services",
"COMPUTER_HARDWARE_AND_SOFTWARE": "Computer Hardware & Software",
"COMPUTER_MAINTENANCE_REPAIR_AND_SERVICES_NOT_ELSEWHERE_CLAS": "Computer Maintenance, Repair And Services Not Elsewhere Clas",
"CONSTRUCTION": "Construction",
"CONSTRUCTION_MATERIALS_NOT_ELSEWHERE_CLASSIFIED": "Construction Materials Not Elsewhere Classified",
"CONSULTING_SERVICES": "Consulting services",
"CONSUMER_CREDIT_REPORTING_AGENCIES": "Consumer Credit Reporting Agencies",
"CONVALESCENT_HOMES": "Convalescent Homes",
"COSMETIC_STORES": "Cosmetic Stores",
"COUNSELING_SERVICES_DEBT_MARRIAGE_PERSONAL": "Counseling Services--Debt,Marriage,Personal",
"COUNTERFEIT_CURRENCY_AND_STAMPS": "Counterfeit Currency and Stamps",
"COUNTERFEIT_ITEMS": "Counterfeit Items",
"COUNTRY_CLUBS": "Country Clubs",
"COURIER_SERVICES": "Courier services",
"COURIER_SERVICES_AIR_AND_GROUND_AND_FREIGHT_FORWARDERS": "Courier Services-Air And Ground,And Freight Forwarders",
"COURT_COSTS_ALIMNY_CHILD_SUPT": "Court Costs/Alimny/Child Supt",
"COURT_COSTS_INCLUDING_ALIMONY_AND_CHILD_SUPPORT_COURTS_OF_LAW": "Court Costs, Including Alimony and Child Support - Courts of Law",
"CREDIT_CARDS": "Credit Cards",
"CREDIT_UNION": "Credit union",
"CULTURE_AND_RELIGION": "Culture & Religion",
"DAIRY_PRODUCTS_STORES": "Dairy Products Stores",
"DANCE_HALLS_STUDIOS_AND_SCHOOLS": "Dance Halls,Studios,And Schools",
"DECORATIVE": "Decorative",
"DENTAL": "Dental",
"DENTISTS_AND_ORTHODONTISTS": "Dentists And Orthodontists",
"DEPARTMENT_STORES": "Department Stores",
"DESKTOP_PCS": "Desktop PCs",
"DEVICES": "Devices",
"DIECAST_TOYS_VEHICLES": "Diecast, Toys Vehicles",
"DIGITAL_GAMES": "Digital games",
"DIGITAL_MEDIA_BOOKS_MOVIES_MUSIC": "Digital media,books,movies,music",
"DIRECT_MARKETING": "Direct Marketing",
"DIRECT_MARKETING_CATALOG_MERCHANT": "Direct Marketing - Catalog Merchant",
"DIRECT_MARKETING_INBOUND_TELE": "Direct Marketing - Inbound Tele",
"DIRECT_MARKETING_OUTBOUND_TELE": "Direct Marketing - Outbound Tele",
"DIRECT_MARKETING_SUBSCRIPTION": "Direct Marketing - Subscription",
"DISCOUNT_STORES": "Discount Stores",
"DOOR_TO_DOOR_SALES": "Door-To-Door Sales",
"DRAPERY_WINDOW_COVERING_AND_UPHOLSTERY": "Drapery, window covering, and upholstery",
"DRINKING_PLACES": "Drinking Places",
"DRUGSTORE": "Drugstore",
"DURABLE_GOODS": "Durable goods",
"ECOMMERCE_DEVELOPMENT": "eCommerce Development",
"ECOMMERCE_SERVICES": "eCommerce Services",
"EDUCATIONAL_AND_TEXTBOOKS": "Educational and textbooks",
"ELECTRIC_RAZOR_STORES": "Electric Razor Stores",
"ELECTRICAL_AND_SMALL_APPLIANCE_REPAIR": "Electrical and small appliance repair",
"ELECTRICAL_CONTRACTORS": "Electrical Contractors",
"ELECTRICAL_PARTS_AND_EQUIPMENT": "Electrical Parts and Equipment",
"ELECTRONIC_CASH": "Electronic Cash",
"ELEMENTARY_AND_SECONDARY_SCHOOLS": "Elementary and secondary schools",
"EMPLOYMENT": "Employment",
"ENTERTAINERS": "Entertainers",
"ENTERTAINMENT_AND_MEDIA": "Entertainment and media",
"EQUIP_TOOL_FURNITURE_AND_APPLIANCE_RENTAL_AND_LEASING": "Equip, Tool, Furniture, And Appliance Rental And Leasing",
"ESCROW": "Escrow",
"EVENT_AND_WEDDING_PLANNING": "Event & Wedding Planning",
"EXERCISE_AND_FITNESS": "Exercise and fitness",
"EXERCISE_EQUIPMENT": "Exercise Equipment",
"EXTERMINATING_AND_DISINFECTING_SERVICES": "Exterminating and disinfecting services",
"FABRICS_AND_SEWING": "Fabrics & Sewing",
"FAMILY_CLOTHING_STORES": "Family Clothing Stores",
"FASHION_JEWELRY": "Fashion jewelry",
"FAST_FOOD_RESTAURANTS": "Fast Food Restaurants",
"FICTION_AND_NONFICTION": "Fiction and nonfiction",
"FINANCE_COMPANY": "Finance company",
"FINANCIAL_AND_INVESTMENT_ADVICE": "Financial and investment advice",
"FINANCIAL_INSTITUTIONS_MERCHANDISE_AND_SERVICES": "Financial Institutions - Merchandise And Services",
"FIREARM_ACCESSORIES": "Firearm accessories",
"FIREARMS_WEAPONS_AND_KNIVES": "Firearms, Weapons and Knives",
"FIREPLACE_AND_FIREPLACE_SCREENS": "Fireplace, and fireplace screens",
"FIREWORKS": "Fireworks",
"FISHING": "Fishing",
"FLORISTS": "Florists",
"FLOWERS": "Flowers",
"FOOD_DRINK_AND_NUTRITION": "Food, Drink & Nutrition",
"FOOD_PRODUCTS": "Food Products",
"FOOD_RETAIL_AND_SERVICE": "Food retail and service",
"FRAGRANCES_AND_PERFUMES": "Fragrances and perfumes",
"FREEZER_AND_LOCKER_MEAT_PROVISIONERS": "Freezer and Locker Meat Provisioners",
"FUEL_DEALERS_FUEL_OIL_WOOD_AND_COAL": "Fuel Dealers-Fuel Oil, Wood & Coal",
"FUEL_DEALERS_NON_AUTOMOTIVE": "Fuel Dealers - Non Automotive",
"FUNERAL_SERVICES_AND_CREMATORIES": "Funeral Services & Crematories",
"FURNISHING_AND_DECORATING": "Furnishing & Decorating",
"FURNITURE": "Furniture",
"FURRIERS_AND_FUR_SHOPS": "Furriers and Fur Shops",
"GADGETS_AND_OTHER_ELECTRONICS": "Gadgets & other electronics",
"GAMBLING": "Gambling",
"GAME_SOFTWARE": "Game Software",
"GAMES": "Games",
"GARDEN_SUPPLIES": "Garden supplies",
"GENERAL": "General",
"GENERAL_CONTRACTORS": "General contractors",
"GENERAL_GOVERNMENT": "General - Government",
"GENERAL_SOFTWARE": "General - Software",
"GENERAL_TELECOM": "General - Telecom",
"GIFTS_AND_FLOWERS": "Gifts and flowers",
"GLASS_PAINT_AND_WALLPAPER_STORES": "Glass,Paint,And Wallpaper Stores",
"GLASSWARE_CRYSTAL_STORES": "Glassware, Crystal Stores",
"GOVERNMENT": "Government",
"GOVERNMENT_IDS_AND_LICENSES": "Government IDs and Licenses",
"GOVERNMENT_LICENSED_ON_LINE_CASINOS_ON_LINE_GAMBLING": "Government Licensed On-Line Casinos - On-Line Gambling",
"GOVERNMENT_OWNED_LOTTERIES": "Government-Owned Lotteries",
"GOVERNMENT_SERVICES": "Government services",
"GRAPHIC_AND_COMMERCIAL_DESIGN": "Graphic & Commercial Design",
"GREETING_CARDS": "Greeting Cards",
"GROCERY_STORES_AND_SUPERMARKETS": "Grocery Stores & Supermarkets",
"HARDWARE_AND_TOOLS": "Hardware & Tools",
"HARDWARE_EQUIPMENT_AND_SUPPLIES": "Hardware, Equipment, and Supplies",
"HAZARDOUS_RESTRICTED_AND_PERISHABLE_ITEMS": "Hazardous, Restricted and Perishable Items",
"HEALTH_AND_BEAUTY_SPAS": "Health and beauty spas",
"HEALTH_AND_NUTRITION": "Health & Nutrition",
"HEALTH_AND_PERSONAL_CARE": "Health and personal care",
"HEARING_AIDS_SALES_AND_SUPPLIES": "Hearing Aids Sales and Supplies",
"HEATING_PLUMBING_AC": "Heating, Plumbing, AC",
"HIGH_RISK_MERCHANT": "High Risk Merchant",
"HIRING_SERVICES": "Hiring services",
"HOBBIES_TOYS_AND_GAMES": "Hobbies, Toys & Games",
"HOME_AND_GARDEN": "Home and garden",
"HOME_AUDIO": "Home Audio",
"HOME_DECOR": "Home decor",
"HOME_ELECTRONICS": "Home Electronics",
"HOSPITALS": "Hospitals",
"HOTELS_MOTELS_INNS_RESORTS": "Hotels/Motels/Inns/Resorts",
"HOUSEWARES": "Housewares",
"HUMAN_PARTS_AND_REMAINS": "Human Parts and Remains",
"HUMOROUS_GIFTS_AND_NOVELTIES": "Humorous Gifts & Novelties",
"HUNTING": "Hunting",
"IDS_LICENSES_AND_PASSPORTS": "IDs, licenses, and passports",
"ILLEGAL_DRUGS_AND_PARAPHERNALIA": "Illegal Drugs & Paraphernalia",
"INDUSTRIAL": "Industrial",
"INDUSTRIAL_AND_MANUFACTURING_SUPPLIES": "Industrial and manufacturing supplies",
"INSURANCE_AUTO_AND_HOME": "Insurance - auto and home",
"INSURANCE_DIRECT": "Insurance - Direct",
"INSURANCE_LIFE_AND_ANNUITY": "Insurance - life and annuity",
"INSURANCE_SALES_UNDERWRITING": "Insurance Sales/Underwriting",
"INSURANCE_UNDERWRITING_PREMIUMS": "Insurance Underwriting, Premiums",
"INTERNET_AND_NETWORK_SERVICES": "Internet & Network Services",
"INTRA_COMPANY_PURCHASES": "Intra-Company Purchases",
"LABORATORIES_DENTAL_MEDICAL": "Laboratories-Dental/Medical",
"LANDSCAPING": "Landscaping",
"LANDSCAPING_AND_HORTICULTURAL_SERVICES": "Landscaping And Horticultural Services",
"LAUNDRY_CLEANING_SERVICES": "Laundry, Cleaning Services",
"LEGAL": "Legal",
"LEGAL_SERVICES_AND_ATTORNEYS": "Legal services and attorneys",
"LOCAL_DELIVERY_SERVICE": "Local delivery service",
"LOCKSMITH": "Locksmith",
"LODGING_AND_ACCOMMODATIONS": "Lodging and accommodations",
"LOTTERY_AND_CONTESTS": "Lottery and contests",
"LUGGAGE_AND_LEATHER_GOODS": "Luggage and leather goods",
"LUMBER_AND_BUILDING_MATERIALS": "Lumber & Building Materials",
"MAGAZINES": "Magazines",
"MAINTENANCE_AND_REPAIR_SERVICES": "Maintenance and repair services",
"MAKEUP_AND_COSMETICS": "Makeup and cosmetics",
"MANUAL_CASH_DISBURSEMENTS": "Manual Cash Disbursements",
"MASSAGE_PARLORS": "Massage Parlors",
"MEDICAL": "Medical",
"MEDICAL_AND_PHARMACEUTICAL": "Medical & Pharmaceutical",
"MEDICAL_CARE": "Medical care",
"MEDICAL_EQUIPMENT_AND_SUPPLIES": "Medical equipment and supplies",
"MEDICAL_SERVICES": "Medical Services",
"MEETING_PLANNERS": "Meeting Planners",
"MEMBERSHIP_CLUBS_AND_ORGANIZATIONS": "Membership clubs and organizations",
"MEMBERSHIP_COUNTRY_CLUBS_GOLF": "Membership/Country Clubs/Golf",
"MEMORABILIA": "Memorabilia",
"MEN_AND_BOY_CLOTHING_AND_ACCESSORY_STORES": "Men's And Boy's Clothing And Accessory Stores",
"MEN_CLOTHING": "Men's Clothing",
"MERCHANDISE": "Merchandise",
"METAPHYSICAL": "Metaphysical",
"MILITARIA": "Militaria",
"MILITARY_AND_CIVIL_SERVICE_UNIFORMS": "Military and civil service uniforms",
"MISC_AUTOMOTIVE_AIRCRAFT_AND_FARM_EQUIPMENT_DEALERS": "Misc,Automotive,Aircraft,And Farm Equipment Dealers",
"MISC_GENERAL_MERCHANDISE": "Misc General Merchandise",
"MISCELLANEOUS_GENERAL_SERVICES": "Miscellaneous General Services",
"MISCELLANEOUS_REPAIR_SHOPS_AND_RELATED_SERVICES": "Miscellaneous Repair Shops And Related Services",
"MODEL_KITS": "Model Kits",
"MONEY_TRANSFER_MEMBER_FINANCIAL_INSTITUTION": "Money Transfer - Member Financial Institution",
"MONEY_TRANSFER_MERCHANT": "Money Transfer--Merchant",
"MOTION_PICTURE_THEATERS": "Motion Picture Theaters",
"MOTOR_FREIGHT_CARRIERS_AND_TRUCKING": "Motor Freight Carriers & Trucking",
"MOTOR_HOME_AND_RECREATIONAL_VEHICLE_RENTAL": "Motor Home And Recreational Vehicle Rental",
"MOTOR_HOMES_DEALERS": "Motor Homes Dealers",
"MOTOR_VEHICLE_SUPPLIES_AND_NEW_PARTS": "Motor Vehicle Supplies and New Parts",
"MOTORCYCLE_DEALERS": "Motorcycle Dealers",
"MOTORCYCLES": "Motorcycles",
"MOVIE": "Movie",
"MOVIE_TICKETS": "Movie tickets",
"MOVING_AND_STORAGE": "Moving and storage",
"MULTI_LEVEL_MARKETING": "Multi-level marketing",
"MUSIC_CDS_CASSETTES_AND_ALBUMS": "Music - CDs, cassettes and albums",
"MUSIC_STORE_INSTRUMENTS_AND_SHEET_MUSIC": "Music store - instruments and sheet music",
"NETWORKING": "Networking",
"NEW_AGE": "New Age",
"NEW_PARTS_AND_SUPPLIES_MOTOR_VEHICLE": "New parts and supplies - motor vehicle",
"NEWS_DEALERS_AND_NEWSTANDS": "News Dealers and Newstands",
"NON_DURABLE_GOODS": "Non-durable goods",
"NON_FICTION": "Non-Fiction",
"NON_PROFIT_POLITICAL_AND_RELIGION": "Non-Profit, Political & Religion",
"NONPROFIT": "Nonprofit",
"NOVELTIES": "Novelties",
"OEM_SOFTWARE": "Oem Software",
"OFFICE_SUPPLIES_AND_EQUIPMENT": "Office Supplies and Equipment",
"ONLINE_DATING": "Online Dating",
"ONLINE_GAMING": "Online gaming",
"ONLINE_GAMING_CURRENCY": "Online gaming currency",
"ONLINE_SERVICES": "online services",
"OOUTBOUND_TELEMARKETING_MERCH": "Ooutbound Telemarketing Merch",
"OPHTHALMOLOGISTS_OPTOMETRIST": "Ophthalmologists/Optometrist",
"OPTICIANS_AND_DISPENSING": "Opticians And Dispensing",
"ORTHOPEDIC_GOODS_PROSTHETICS": "Orthopedic Goods/Prosthetics",
"OSTEOPATHS": "Osteopaths",
"OTHER": "Other",
"PACKAGE_TOUR_OPERATORS": "Package Tour Operators",
"PAINTBALL": "Paintball",
"PAINTS_VARNISHES_AND_SUPPLIES": "Paints, Varnishes, and Supplies",
"PARKING_LOTS_AND_GARAGES": "Parking Lots & Garages",
"PARTS_AND_ACCESSORIES": "Parts and accessories",
"PAWN_SHOPS": "Pawn Shops",
"PAYCHECK_LENDER_OR_CASH_ADVANCE": "Paycheck lender or cash advance",
"PERIPHERALS": "Peripherals",
"PERSONALIZED_GIFTS": "Personalized Gifts",
"PET_SHOPS_PET_FOOD_AND_SUPPLIES": "Pet shops, pet food, and supplies",
"PETROLEUM_AND_PETROLEUM_PRODUCTS": "Petroleum and Petroleum Products",
"PETS_AND_ANIMALS": "Pets and animals",
"PHOTOFINISHING_LABORATORIES_PHOTO_DEVELOPING": "Photofinishing Laboratories,Photo Developing",
"PHOTOGRAPHIC_STUDIOS_PORTRAITS": "Photographic studios - portraits",
"PHOTOGRAPHY": "Photography",
"PHYSICAL_GOOD": "Physical Good",
"PICTURE_VIDEO_PRODUCTION": "Picture/Video Production",
"PIECE_GOODS_NOTIONS_AND_OTHER_DRY_GOODS": "Piece Goods Notions and Other Dry Goods",
"PLANTS_AND_SEEDS": "Plants and Seeds",
"PLUMBING_AND_HEATING_EQUIPMENTS_AND_SUPPLIES": "Plumbing & Heating Equipments & Supplies",
"POLICE_RELATED_ITEMS": "Police-Related Items",
"POLITICAL_ORGANIZATIONS": "Politcal Organizations",
"POSTAL_SERVICES_GOVERNMENT_ONLY": "Postal Services - Government Only",
"POSTERS": "Posters",
"PREPAID_AND_STORED_VALUE_CARDS": "Prepaid and stored value cards",
"PRESCRIPTION_DRUGS": "Prescription Drugs",
"PROMOTIONAL_ITEMS": "Promotional Items",
"PUBLIC_WAREHOUSING_AND_STORAGE": "Public Warehousing and Storage",
"PUBLISHING_AND_PRINTING": "Publishing and printing",
"PUBLISHING_SERVICES": "Publishing Services",
"RADAR_DECTORS": "Radar Dectors",
"RADIO_TELEVISION_AND_STEREO_REPAIR": "Radio, television, and stereo repair",
"REAL_ESTATE": "Real Estate",
"REAL_ESTATE_AGENT": "Real estate agent",
"REAL_ESTATE_AGENTS_AND_MANAGERS_RENTALS": "Real Estate Agents And Managers - Rentals",
"RELIGION_AND_SPIRITUALITY_FOR_PROFIT": "Religion and spirituality for profit",
"RELIGIOUS": "Religious",
"RELIGIOUS_ORGANIZATIONS": "Religious Organizations",
"REMITTANCE": "Remittance",
"RENTAL_PROPERTY_MANAGEMENT": "Rental property management",
"RESIDENTIAL": "Residential",
"RETAIL": "Retail",
"RETAIL_FINE_JEWELRY_AND_WATCHES": "Retail - fine jewelry and watches",
"REUPHOLSTERY_AND_FURNITURE_REPAIR": "Reupholstery and furniture repair",
"RINGS": "Rings",
"ROOFING_SIDING_SHEET_METAL": "Roofing/Siding, Sheet Metal",
"RUGS_AND_CARPETS": "Rugs & Carpets",
"SCHOOLS_AND_COLLEGES": "Schools and Colleges",
"SCIENCE_FICTION": "Science Fiction",
"SCRAPBOOKING": "Scrapbooking",
"SCULPTURES": "Sculptures",
"SECURITIES_BROKERS_AND_DEALERS": "Securities - Brokers And Dealers",
"SECURITY_AND_SURVEILLANCE": "Security and surveillance",
"SECURITY_AND_SURVEILLANCE_EQUIPMENT": "Security and surveillance equipment",
"SECURITY_BROKERS_AND_DEALERS": "Security brokers and dealers",
"SEMINARS": "Seminars",
"SERVICE_STATIONS": "Service Stations",
"SERVICES": "Services",
"SEWING_NEEDLEWORK_FABRIC_AND_PIECE_GOODS_STORES": "Sewing,Needlework,Fabric And Piece Goods Stores",
"SHIPPING_AND_PACKING": "Shipping & Packaging",
"SHOE_REPAIR_HAT_CLEANING": "Shoe Repair/Hat Cleaning",
"SHOE_STORES": "Shoe Stores",
"SHOES": "Shoes",
"SNOWMOBILE_DEALERS": "Snowmobile Dealers",
"SOFTWARE": "Software",
"SPECIALTY_AND_MISC_FOOD_STORES": "Specialty and misc food stores",
"SPECIALTY_CLEANING_POLISHING_AND_SANITATION_PREPARATIONS": "Specialty Cleaning, Polishing And Sanitation Preparations",
"SPECIALTY_OR_RARE_PETS": "Specialty or rare pets",
"SPORT_GAMES_AND_TOYS": "Sport games and toys",
"SPORTING_AND_RECREATIONAL_CAMPS": "Sporting And Recreational Camps",
"SPORTING_GOODS": "Sporting Goods",
"SPORTS_AND_OUTDOORS": "Sports and outdoors",
"SPORTS_AND_RECREATION": "Sports & Recreation",
"STAMP_AND_COIN": "Stamp and coin",
"STATIONARY_PRINTING_AND_WRITING_PAPER": "Stationary, printing, and writing paper",
"STENOGRAPHIC_AND_SECRETARIAL_SUPPORT_SERVICES": "Stenographic and secretarial support services",
"STOCKS_BONDS_SECURITIES_AND_RELATED_CERTIFICATES": "Stocks, Bonds, Securities and Related Certificates",
"STORED_VALUE_CARDS": "Stored Value Cards",
"SUPPLIES": "Supplies",
"SUPPLIES_AND_TOYS": "Supplies & Toys",
"SURVEILLANCE_EQUIPMENT": "Surveillance Equipment",
"SWIMMING_POOLS_AND_SPAS": "Swimming Pools & Spas",
"SWIMMING_POOLS_SALES_SUPPLIES_SERVICES": "Swimming Pools-Sales,Supplies,Services",
"TAILORS_AND_ALTERATIONS": "Tailors and alterations",
"TAX_PAYMENTS": "Tax Payments",
"TAX_PAYMENTS_GOVERNMENT_AGENCIES": "Tax Payments - Government Agencies",
"TAXICABS_AND_LIMOUSINES": "Taxicabs and limousines",
"TELECOMMUNICATION_SERVICES": "Telecommunication Services",
"TELEPHONE_CARDS": "Telephone Cards",
"TELEPHONE_EQUIPMENT": "Telephone Equipment",
"TELEPHONE_SERVICES": "Telephone Services",
"THEATER": "Theater",
"TIRE_RETREADING_AND_REPAIR": "Tire Retreading and Repair",
"TOLL_OR_BRIDGE_FEES": "Toll or Bridge Fees",
"TOOLS_AND_EQUIPMENT": "Tools and equipment",
"TOURIST_ATTRACTIONS_AND_EXHIBITS": "Tourist Attractions And Exhibits",
"TOWING_SERVICE": "Towing service",
"TOYS_AND_GAMES": "Toys and games",
"TRADE_AND_VOCATIONAL_SCHOOLS": "Trade And Vocational Schools",
"TRADEMARK_INFRINGEMENT": "Trademark Infringement",
"TRAILER_PARKS_AND_CAMPGROUNDS": "Trailer Parks And Campgrounds",
"TRAINING_SERVICES": "Training services",
"TRANSPORTATION_SERVICES": "Transportation Services",
"TRAVEL": "Travel",
"TRUCK_AND_UTILITY_TRAILER_RENTALS": "Truck And Utility Trailer Rentals",
"TRUCK_STOP": "Truck Stop",
"TYPESETTING_PLATE_MAKING_AND_RELATED_SERVICES": "Typesetting, Plate Making, and Related Services",
"USED_MERCHANDISE_AND_SECONDHAND_STORES": "Used Merchandise And Secondhand Stores",
"USED_PARTS_MOTOR_VEHICLE": "Used parts - motor vehicle",
"UTILITIES": "Utilities",
"UTILITIES_ELECTRIC_GAS_WATER_SANITARY": "Utilities - Electric,Gas,Water,Sanitary",
"VARIETY_STORES": "Variety Stores",
"VEHICLE_SALES": "Vehicle sales",
"VEHICLE_SERVICE_AND_ACCESSORIES": "Vehicle service and accessories",
"VIDEO_EQUIPMENT": "Video Equipment",
"VIDEO_GAME_ARCADES_ESTABLISH": "Video Game Arcades/Establish",
"VIDEO_GAMES_AND_SYSTEMS": "Video Games & Systems",
"VIDEO_TAPE_RENTAL_STORES": "Video Tape Rental Stores",
"VINTAGE_AND_COLLECTIBLE_VEHICLES": "Vintage and Collectible Vehicles",
"VINTAGE_AND_COLLECTIBLES": "Vintage and collectibles",
"VITAMINS_AND_SUPPLEMENTS": "Vitamins & Supplements",
"VOCATIONAL_AND_TRADE_SCHOOLS": "Vocational and trade schools",
"WATCH_CLOCK_AND_JEWELRY_REPAIR": "Watch, clock, and jewelry repair",
"WEB_HOSTING_AND_DESIGN": "Web hosting and design",
"WELDING_REPAIR": "Welding Repair",
"WHOLESALE_CLUBS": "Wholesale Clubs",
"WHOLESALE_FLORIST_SUPPLIERS": "Wholesale Florist Suppliers",
"WHOLESALE_PRESCRIPTION_DRUGS": "Wholesale Prescription Drugs",
"WILDLIFE_PRODUCTS": "Wildlife Products",
"WIRE_TRANSFER": "Wire Transfer",
"WIRE_TRANSFER_AND_MONEY_ORDER": "Wire transfer and money order",
"WOMEN_ACCESSORY_SPECIALITY": "Women's Accessory/Speciality",
"WOMEN_CLOTHING": "Women's clothing"
}
}