@fcgs/filerr-nach
Version:
nACH is a highly customizable Node.js module exposing a high & low-level API for generating ACH files for use within the ACH network
194 lines (165 loc) • 5.94 kB
JavaScript
// Utility Functions
var _ = require('lodash');
var moment = require('moment');
var nACHError = require('./error');
var counter = 0;
// Pad a given string to a fixed width using any character or number (defaults to one blank space)
// Both a string and width are required params for this function, but it also takes two optional
// parameters. First, a boolean called 'padRight' which by default is true. This means padding
// will be applied to the right side of the string. Setting this to false will pad the left side of the
// string. You can also specify the character you want to use to pad the string.
function pad(string, width) {
var padRight;
var padChar;
var result;
var string = string + '';
if (typeof arguments[2] == 'boolean') {
padRight = arguments[2];
padChar = arguments[3] || ' ';
} else if (typeof arguments[3] == 'boolean') {
padRight = arguments[3];
padChar = arguments[2];
} else {
padRight = true; // padRight is true be default
padChar = arguments[2] || ' '; // The padding character is just a space by default
}
if (string.length >= width) {
return string;
} else {
result = new Array(width - string.length + 1).join(padChar);
return padRight ? string + result : result + string;
}
}
function computeCheckDigit(routing) {
var a = routing.split('').map(Number);
return a.length !== 8
? routing
: routing +
((7 * (a[0] + a[3] + a[6]) +
3 * (a[1] + a[4] + a[7]) +
9 * (a[2] + a[5])) %
10);
}
// This function is passed a field and a regex and tests the field's value property against the given regex
function testRegex(regex, field) {
var string = field.number
? parseFloat(field.value).toFixed(2).replace(/\./, '')
: field.value;
if (!regex.test(string)) {
throw new nACHError({
name: 'Invalid Data Type',
message:
field.name +
"'s data type is required to be " +
field.type +
", but its contents don't reflect that.",
});
}
return true;
}
// This function iterates through the object passed in and checks to see if it has a "position" property. If so, we pad it, and then concatentate it where belongs.
function generateString(object, cb) {
var counter = 1;
var result = '';
var objectCount;
// How does this actually work? It doens't seem like this is enough protection from iterating infinitely.
objectCount = _.size(object);
while (counter < objectCount) {
_.forEach(object, function (field) {
if (field.position === counter) {
if (field.blank === true || field.type == 'alphanumeric') {
result = result + pad(field.value, field.width);
} else {
var string = field.number
? parseFloat(field.value).toFixed(2).replace(/\./, '')
: field.value;
var paddingChar = field.paddingChar || '0';
result = result + pad(string, field.width, false, paddingChar);
}
counter++;
}
});
}
cb(result);
}
function parseLine(str, object) {
var result = {};
var pos = 0;
Object.keys(object).forEach((key) => {
var field = object[key];
result[key] = str.substr(pos, field.width).trim();
pos += field.width;
});
return result;
}
function unique() {
return counter++;
}
function overrideLowLevel(values, options, self) {
// For each override value, check to see if it exists on the options object & if so, set it
_.forEach(values, function (field) {
if (options[field]) {
self.set(field, options[field]);
}
});
}
function getNextMultiple(value, multiple) {
return value % multiple == 0
? value
: value + (multiple - (value % multiple));
}
function getNextMultipleDiff(value, multiple) {
return getNextMultiple(value, multiple) - value;
}
// This allows us to create a valid ACH date in the YYMMDD format
var formatDate = function (date) {
var year = pad(date.getFullYear().toString().slice(-2), 2, false, '0');
var month = pad((date.getMonth() + 1).toString(), 2, false, '0');
var day = pad(date.getDate().toString(), 2, false, '0');
return year + month + day;
};
// Create a valid timestamp used by the ACH system in the HHMM format
var formatTime = function (date) {
var hour = date.getHours().toString();
var minute = date.getMinutes().toString();
return pad(hour, 2, false, '0') + pad(minute, 2, false, '0');
};
var isBusinessDay = (module.exports.isBusinessDay = function (day) {
var d = moment(day).day();
return d !== 0 && d !== 6 ? true : false;
});
// This function takes an optional starting date to iterate from based
var computeBusinessDay = (module.exports.computeBusinessDay = function (
businessDays
) {
var day = arguments[1] ? moment(arguments[1]) : moment();
var counter = 0;
function addDays() {
day.add(1, 'days');
if (isBusinessDay(day)) {
counter++;
}
return counter === businessDays ? day.toDate() : addDays();
}
return addDays();
});
var newLineChar = function () {
return '\n';
};
function createEmptyString(length) {
return ' '.repeat(length);
}
module.exports.pad = pad;
module.exports.unique = unique;
module.exports.testRegex = testRegex;
module.exports.formatDate = formatDate;
module.exports.formatTime = formatTime;
module.exports.newLineChar = newLineChar;
module.exports.generateString = generateString;
module.exports.parseLine = parseLine;
module.exports.getNextMultiple = getNextMultiple;
module.exports.overrideLowLevel = overrideLowLevel;
module.exports.computeCheckDigit = computeCheckDigit;
module.exports.computeBusinessDay = computeBusinessDay;
module.exports.getNextMultipleDiff = getNextMultipleDiff;
module.exports.createEmptyString = createEmptyString;