init
11
.arcconfig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"phabricator.uri" : "http://10.0.10.242",
|
||||||
|
|
||||||
|
"load": [
|
||||||
|
".arcanist-extensions/tap_test_engine"
|
||||||
|
],
|
||||||
|
|
||||||
|
"unit.engine": "TAPTestEngine",
|
||||||
|
"unit.engine.tap.command": "gulp --cwd node_server --reporter tap test",
|
||||||
|
"unit.engine.tap.eol": "\n"
|
||||||
|
}
|
18
.arclint
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"linters": {
|
||||||
|
"eslint-regex-based": {
|
||||||
|
"type": "script-and-regex",
|
||||||
|
"include": "(\\.js?$)",
|
||||||
|
"exclude": [],
|
||||||
|
"script-and-regex.script": "node eslint-for-arc.js",
|
||||||
|
"script-and-regex.regex": "/^(?P<file>.*): line (?P<line>[0-9]*), col (?P<char>[0-9]*), ((?P<warning>Warning)|(?P<error>Error)) - (?P<message>.*) \\((?P<code>[a-z-\\/]+)\\)$/m"
|
||||||
|
},
|
||||||
|
"nsp-regex-based": {
|
||||||
|
"type": "script-and-regex",
|
||||||
|
"include": "(package.json$)",
|
||||||
|
"exclude": [],
|
||||||
|
"script-and-regex.script": "node nsp-for-arc.js",
|
||||||
|
"script-and-regex.regex": "/^ (?P<name>\\S* +\\S*) +(?P<original>\\S*) +(?P<other>(?>\\S*(?> > )?)*) +(?P<message>https:\\S*) *$/m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
.eslintrc.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
module.exports = {
|
||||||
|
"extends": [
|
||||||
|
"canonical",
|
||||||
|
"canonical/lodash",
|
||||||
|
"canonical/mocha"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
// enable additional rules
|
||||||
|
//
|
||||||
|
// A reasonable number of these are because we are stuck on Node 4.
|
||||||
|
// Moving to later versions of node will allow things like await/async etc.
|
||||||
|
//
|
||||||
|
"arrow-body-style": 0,
|
||||||
|
"jsdoc/require-description-complete-sentence": 0,
|
||||||
|
"filenames/match-regex": 0,
|
||||||
|
"func-style": 0,
|
||||||
|
"id-length": [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
"exceptions": [
|
||||||
|
"i",
|
||||||
|
"j",
|
||||||
|
"Q",
|
||||||
|
"P",
|
||||||
|
"R",
|
||||||
|
"$",
|
||||||
|
"_"
|
||||||
|
],
|
||||||
|
"max": 50,
|
||||||
|
"min": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id-match": [
|
||||||
|
2,
|
||||||
|
"(^[$A-Za-z]+(?:[A-Z][a-z]*)*\\d*$)|(^[A-Z]+(_[A-Z]+)*(_\\d$)*$)|(^(_|\\$)$)",
|
||||||
|
{
|
||||||
|
"onlyDeclarations": true,
|
||||||
|
"properties": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indent": [
|
||||||
|
2,
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
"SwitchCase": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/no-commonjs": 0,
|
||||||
|
"import/no-dynamic-require": 0,
|
||||||
|
"import/unambiguous": 0,
|
||||||
|
"import/order": 0,
|
||||||
|
"linebreak-style": 0,
|
||||||
|
"lines-around-directive": 0,
|
||||||
|
"line-comment-position": 0,
|
||||||
|
"newline-after-var": 0,
|
||||||
|
"newline-before-return": 0,
|
||||||
|
"no-extra-parens": 0,
|
||||||
|
"no-inline-comments": 0,
|
||||||
|
"no-multi-spaces": [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"ignoreEOLComments": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-param-reassign": 0,
|
||||||
|
"no-trailing-spaces": [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"skipBlankLines": false,
|
||||||
|
"ignoreComments": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-use-before-define": [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"functions": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"object-shorthand": 1,
|
||||||
|
"promise/prefer-await-to-then": 0,
|
||||||
|
"promise/prefer-await-to-callbacks": 0,
|
||||||
|
"sort-keys": 0,
|
||||||
|
"space-before-function-paren": [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"anonymous": "never",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"strict": 0
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"jsdoc": {
|
||||||
|
"additionalTagNames": {
|
||||||
|
"customTags": ["ngInject"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
19
.gitattributes
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# All other files are subjected to the usual algorithm to determine
|
||||||
|
# whether a file is a binary file or a text file, respecting
|
||||||
|
# "core.eol" for all files detected as text files.
|
||||||
|
# "core.autocrlf", if set, will force the conversion to/from CRLF
|
||||||
|
# automatically as necessary for text files.
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# package.json (from NPM) is in lf not crlf
|
||||||
|
package.json eol=lf
|
||||||
|
package-lock.json eol=lf
|
||||||
|
|
||||||
|
# shell scripts should be LF
|
||||||
|
*.sh eol=lf
|
||||||
|
|
||||||
|
# jade/pug files are lf too
|
||||||
|
# see: https://github.com/jadejs/jade/issues/1683
|
||||||
|
*.jade eol=lf
|
||||||
|
*.pug eol=lf
|
||||||
|
|
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.idea/
|
||||||
|
node_server/node_modules/
|
||||||
|
/node_server/portal/
|
||||||
|
/node_server/docs/
|
||||||
|
/node_server/temp/
|
||||||
|
tsconfig.json
|
||||||
|
devenvtemp.txt
|
||||||
|
testenvtemp.txt
|
||||||
|
prodenvtemp.txt
|
||||||
|
version.txt
|
||||||
|
/node_modules/
|
||||||
|
node_server/coverage/
|
||||||
|
node_server/email_templates/
|
4
.gitmodules
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[submodule ".arcanist-extensions"]
|
||||||
|
path = .arcanist-extensions
|
||||||
|
url = https://github.com/farrago/arcanist-extensions.git
|
||||||
|
branch = tap-line-endings
|
28
Dockerfile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
FROM registry.eu-gb.bluemix.net/ibmnode:latest
|
||||||
|
|
||||||
|
## Add the node server directory.
|
||||||
|
ADD ./node_server /node_server
|
||||||
|
|
||||||
|
## Standard updates. Note to pin a version use: package-foo=1.3.* \
|
||||||
|
RUN apt-get update -q && apt-get install -y -q \
|
||||||
|
curl \
|
||||||
|
graphicsmagick \
|
||||||
|
&& apt-get clean -q \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
## Enhance default password rules.
|
||||||
|
RUN sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' /etc/login.defs
|
||||||
|
|
||||||
|
## Enhance default password rules.
|
||||||
|
RUN echo 'Europe/London' > /etc/timezone
|
||||||
|
RUN dpkg-reconfigure -f noninteractive tzdata
|
||||||
|
|
||||||
|
## Expose the appropriate ports.
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
## Install the node modules.
|
||||||
|
WORKDIR /node_server
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
## Execute the code.
|
||||||
|
CMD ["node", "node_server.js"]
|
76
bitbucket-pipelines.yml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# This is based on the sample build configuration for JavaScript.
|
||||||
|
# Check our guides at https://confluence.atlassian.com/x/14UWN for more examples.
|
||||||
|
# Only use spaces to indent your .yml configuration.
|
||||||
|
# -----
|
||||||
|
# You can specify a custom docker image from Docker Hub as your build environment.
|
||||||
|
image: node:8
|
||||||
|
|
||||||
|
pipelines:
|
||||||
|
default:
|
||||||
|
- step:
|
||||||
|
#
|
||||||
|
# Validate swagger definitions against the swagger 2.0 JSON schema spec
|
||||||
|
# NOTE: we have to download it manually as ajv-cli only supports local
|
||||||
|
# schemas at present.
|
||||||
|
#
|
||||||
|
name: "Swagger schema validation"
|
||||||
|
caches:
|
||||||
|
- node
|
||||||
|
script:
|
||||||
|
- npm install -g ajv-cli
|
||||||
|
- TEMPLATEFILE=`mktemp` || exit 1
|
||||||
|
- wget -q http://json.schemastore.org/swagger-2.0 -O $TEMPLATEFILE
|
||||||
|
- DEREFEDSWAGGERFILE=`mktemp` || exit 1
|
||||||
|
- npm install -g json-refs
|
||||||
|
- json-refs resolve node_server/swagger_api/api_swagger_def.json > $DEREFEDSWAGGERFILE
|
||||||
|
- ajv test -s $TEMPLATEFILE -d $DEREFEDSWAGGERFILE --valid --errors=json
|
||||||
|
- ajv test -s $TEMPLATEFILE -d node_server/integration_api/integration_swagger_def.json --valid --errors=json
|
||||||
|
- step:
|
||||||
|
#
|
||||||
|
# Run ESLint against all the JS files that were changed in this branch.
|
||||||
|
#
|
||||||
|
# The linting is run using a script as its a little too complex for
|
||||||
|
# standalone commands.
|
||||||
|
#
|
||||||
|
# Note that this will also run on master when changes are merged in, but
|
||||||
|
# as `HEAD` and `master` will be the same revision there will be no files
|
||||||
|
# in the list of changes and ESLint won't be run.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
name: "ESLint"
|
||||||
|
caches:
|
||||||
|
- node
|
||||||
|
script:
|
||||||
|
# Install packages to get our expected version of ESLint and related configs
|
||||||
|
# pipeline runs as root, but npm doesn't like installing packages as
|
||||||
|
# root for security reasons. As this is just CI we allow it, using
|
||||||
|
# unsafe-perm to make npm accept it.
|
||||||
|
#
|
||||||
|
- npm install --unsafe-perm
|
||||||
|
# Run the tests
|
||||||
|
- chmod +x ./tools/bitbucket-pipeline-scripts/eslint-changes.sh
|
||||||
|
- ./tools/bitbucket-pipeline-scripts/eslint-changes.sh
|
||||||
|
- step:
|
||||||
|
#
|
||||||
|
# All unit tests are run every time in case a change has an unexpected
|
||||||
|
# effect on other areas.
|
||||||
|
#
|
||||||
|
name: "Unit tests"
|
||||||
|
caches:
|
||||||
|
- node
|
||||||
|
script:
|
||||||
|
- npm install -g gulp
|
||||||
|
#
|
||||||
|
# pipeline runs as root, but npm doesn't like installing packages as
|
||||||
|
# root for security reasons. As this is just CI we allow it, using
|
||||||
|
# unsafe-perm to make npm accept it.
|
||||||
|
#
|
||||||
|
- npm install --unsafe-perm
|
||||||
|
#
|
||||||
|
# As described in the docs, we need to use the mccha-junit-reporter
|
||||||
|
# to output results in a format pipelines understands, plus set an
|
||||||
|
# environment variable to put them in a location that it looks in.
|
||||||
|
# See:
|
||||||
|
# https://confluence.atlassian.com/bitbucket/test-reporting-in-pipelines-939708543.html
|
||||||
|
#
|
||||||
|
- MOCHA_FILE=./test-reports/[hash].xml gulp --cwd node_server test --reporter mocha-junit-reporter
|
36
eslint-for-arc.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview There is a compatibility problem between eslint and arcanist.
|
||||||
|
* ESLint returns a non-zero code on exit if there are any lint errors
|
||||||
|
* in the file. But `arc` regex liniters expect to only get an
|
||||||
|
* non-zero exit code on true errors (e.g. eslint config errors).
|
||||||
|
*
|
||||||
|
* So we use this file and the ESLint API to build a version that
|
||||||
|
* only returns non-zero on actual errors (which will be unhandled
|
||||||
|
* exceptions, so we don't actually need to do anything to achieve)
|
||||||
|
*/
|
||||||
|
const CLIEngine = require('eslint').CLIEngine;
|
||||||
|
|
||||||
|
const cli = new CLIEngine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file to lint from the command line. The command line is always
|
||||||
|
* argv[0] - node exe
|
||||||
|
* argv[1] - this script
|
||||||
|
* argv[2] - the file passed on the command line
|
||||||
|
*/
|
||||||
|
if (process.argv.length !== 3) {
|
||||||
|
throw new Error('Must pass exactly 1 file on the command line');
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = process.argv[2];
|
||||||
|
|
||||||
|
// Lint the file passed in
|
||||||
|
const report = cli.executeOnFiles([filename]);
|
||||||
|
|
||||||
|
// Get the compact formatter
|
||||||
|
const formatter = cli.getFormatter('compact');
|
||||||
|
|
||||||
|
// Output to stdout so it can be picked up by the arcanist regex
|
||||||
|
process.stdout.write(formatter(report.results));
|
||||||
|
|
||||||
|
// Allow the program to exit normally, which will return code 0
|
8
node_server/.jscsrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"excludeFiles": ["node_modules/**", "bower_components/**"],
|
||||||
|
"preset": "google",
|
||||||
|
"validateIndentation": 4,
|
||||||
|
"maximumLineLength": 140,
|
||||||
|
"maxErrors": 1000,
|
||||||
|
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties"
|
||||||
|
}
|
58
node_server/.jshintrc
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"bitwise": true,
|
||||||
|
"camelcase": true,
|
||||||
|
"curly": true,
|
||||||
|
"eqeqeq": true,
|
||||||
|
"es3": false,
|
||||||
|
"forin": true,
|
||||||
|
"freeze": true,
|
||||||
|
"immed": true,
|
||||||
|
"indent": 4,
|
||||||
|
"latedef": "nofunc",
|
||||||
|
"newcap": true,
|
||||||
|
"noarg": true,
|
||||||
|
"noempty": true,
|
||||||
|
"nonbsp": true,
|
||||||
|
"nonew": true,
|
||||||
|
"plusplus": false,
|
||||||
|
"quotmark": "single",
|
||||||
|
"undef": true,
|
||||||
|
"unused": false,
|
||||||
|
"strict": false,
|
||||||
|
"maxparams": 10,
|
||||||
|
"maxdepth": 5,
|
||||||
|
"maxstatements": 50,
|
||||||
|
"maxcomplexity": 8,
|
||||||
|
"maxlen": 140,
|
||||||
|
|
||||||
|
"asi": false,
|
||||||
|
"boss": false,
|
||||||
|
"debug": false,
|
||||||
|
"eqnull": true,
|
||||||
|
"esnext": true,
|
||||||
|
"evil": false,
|
||||||
|
"expr": false,
|
||||||
|
"funcscope": false,
|
||||||
|
"globalstrict": false,
|
||||||
|
"iterator": false,
|
||||||
|
"lastsemic": false,
|
||||||
|
"laxbreak": false,
|
||||||
|
"laxcomma": false,
|
||||||
|
"loopfunc": true,
|
||||||
|
"maxerr": 50,
|
||||||
|
"moz": false,
|
||||||
|
"multistr": false,
|
||||||
|
"notypeof": false,
|
||||||
|
"proto": false,
|
||||||
|
"scripturl": false,
|
||||||
|
"shadow": false,
|
||||||
|
"sub": true,
|
||||||
|
"supernew": false,
|
||||||
|
"validthis": false,
|
||||||
|
"noyield": false,
|
||||||
|
|
||||||
|
"node": true,
|
||||||
|
|
||||||
|
"globals": {
|
||||||
|
}
|
||||||
|
}
|
21
node_server/ComServe/auth-promises.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @file This file wraps the functions in auth.js with promises for simpler
|
||||||
|
* use in promises and async/await
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Q = require('q');
|
||||||
|
const auth = require('./auth.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validSession: (...args) => Q.nfapply(auth.validSession, args),
|
||||||
|
validateCurrentSession: (...args) => Q.nfapply(auth.validateCurrentSession, args),
|
||||||
|
checkHMAC: (...args) => Q.nfapply(auth.checkHMAC, args),
|
||||||
|
checkClientPassword: (...args) => Q.nfapply(auth.checkClientPassword, args),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-promise functions for compatibility
|
||||||
|
*/
|
||||||
|
respond: auth.respond,
|
||||||
|
checkClientStatus: auth.checkClientStatus,
|
||||||
|
checkDeviceStatus: auth.checkDeviceStatus
|
||||||
|
};
|
941
node_server/ComServe/auth.js
Normal file
@ -0,0 +1,941 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Authorisation Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var valid = require(global.pathPrefix + 'valid.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var templates = require(global.pathPrefix + '../utils/templates.js');
|
||||||
|
var formattingUtils = require(global.pathPrefix + '../utils/formatting.js');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var async = require('async');
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function checks the client status for any blocking flags. It will return an error if the following are true:
|
||||||
|
* 1) The client is not verified.
|
||||||
|
* 2) The client is barred.
|
||||||
|
*
|
||||||
|
* @type {function} checkClientStatus
|
||||||
|
* @param {!string} ClientStatus - The ClientStatus flag from the client document.
|
||||||
|
*/
|
||||||
|
exports.checkClientStatus = function(ClientStatus) {
|
||||||
|
/**
|
||||||
|
* Valid session. Check client status.
|
||||||
|
*/
|
||||||
|
if (!utils.bitsAllSet(ClientStatus, utils.ClientEmailVerifiedMask)) {
|
||||||
|
return utils.createError(114, 'Client e-mail has not been verified - please click the link.');
|
||||||
|
}
|
||||||
|
if (utils.bitsAllSet(ClientStatus, utils.ClientBarredMask)) {
|
||||||
|
return utils.createError(117, 'Client barred by Comcarde.');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function checks the device status for any blocking flags. It will return an error if the following are true:
|
||||||
|
* 1) The device is not verified.
|
||||||
|
* 2) The device is not authorised.
|
||||||
|
* 3) The device is suspended.
|
||||||
|
* 4) The device is barred.
|
||||||
|
*
|
||||||
|
* @type {function} checkDeviceStatus
|
||||||
|
* @param {!string} DeviceStatus - The DeviceStatus flag from the device document.
|
||||||
|
*/
|
||||||
|
exports.checkDeviceStatus = function(DeviceStatus) {
|
||||||
|
/**
|
||||||
|
* Valid session. Check device status.
|
||||||
|
*/
|
||||||
|
if (!utils.bitsAllSet(DeviceStatus, utils.DeviceRegister2Mask)) {
|
||||||
|
return utils.createError(109, 'Device not verified - SMS not confirmed.');
|
||||||
|
}
|
||||||
|
if (!utils.bitsAllSet(DeviceStatus, utils.DeviceRegister3Mask)) {
|
||||||
|
return utils.createError(110, 'Device not authorised - PIN not set.');
|
||||||
|
}
|
||||||
|
if (utils.bitsAllSet(DeviceStatus, utils.DeviceSuspendedMask)) {
|
||||||
|
return utils.createError(111, 'Device suspended by the user.');
|
||||||
|
}
|
||||||
|
if (utils.bitsAllSet(DeviceStatus, utils.DeviceBarredMask)) {
|
||||||
|
return utils.createError(112, 'Device barred by Comcarde.');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function needs to be called with all server requests. It checks the user is currently logged in
|
||||||
|
* and if they are, it returns their client and device details. It requires two parameters:
|
||||||
|
*
|
||||||
|
* @type {function} validateCurrentSession
|
||||||
|
* @param {!string} DeviceToken - The token assigned to the device at registration.
|
||||||
|
* @param {!string} SessionToken - The token returned at login. Note that this is valid for ~5 minutes only.
|
||||||
|
* Calling this function extends the SessionToken's life.
|
||||||
|
* @param {!function} next - Not optional and should contain the code to be subsequently executed.
|
||||||
|
*/
|
||||||
|
exports.validateCurrentSession = function(DeviceToken, SessionToken, next) {
|
||||||
|
/**
|
||||||
|
* Valid input. Check to see if the database is online.
|
||||||
|
* Cyclomatic complexity is known to be high for this function.
|
||||||
|
*/
|
||||||
|
//jshint -W074
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceToken: DeviceToken}, undefined, false, function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(104, 'Database offline.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No information returned from database.
|
||||||
|
*/
|
||||||
|
if (existingDevice === null) {
|
||||||
|
next(utils.createError(103, 'Cannot find device token.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device found. Now check the token.
|
||||||
|
*/
|
||||||
|
if (SessionToken !== existingDevice.SessionToken) {
|
||||||
|
// Session token invalid.
|
||||||
|
next(utils.createError(107, 'Invalid session token.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the session token expiry.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var expiry = existingDevice.SessionTokenExpiry;
|
||||||
|
if (timestamp >= expiry) {
|
||||||
|
// Session token invalid.
|
||||||
|
next(utils.createError(108, 'Session token expired.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check device status.
|
||||||
|
*/
|
||||||
|
var currentDeviceStatus = exports.checkDeviceStatus(existingDevice.DeviceStatus);
|
||||||
|
if (currentDeviceStatus) {
|
||||||
|
next(currentDeviceStatus, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Through device checks. Pull the client.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientID: existingDevice.ClientID}, undefined, false,
|
||||||
|
function(err, existingClient) {
|
||||||
|
/**
|
||||||
|
* Check for an error.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
// Database is not working.
|
||||||
|
next(utils.createError(105, 'Database offline.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If null then there is no Client account.
|
||||||
|
*/
|
||||||
|
if (existingClient === null) {
|
||||||
|
// Callback.
|
||||||
|
next(utils.createError(106, 'Cannot find account.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check client status.
|
||||||
|
*/
|
||||||
|
var currentClientStatus = exports.checkClientStatus(existingClient.ClientStatus);
|
||||||
|
if (currentClientStatus) {
|
||||||
|
next(currentClientStatus, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Great. All active. Extend token validity.
|
||||||
|
*/
|
||||||
|
var newExpiry = new Date(timestamp);
|
||||||
|
newExpiry.setMinutes(newExpiry.getMinutes() + utils.sessionTimeout);
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: DeviceToken}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
SessionTokenExpiry: newExpiry
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(119, 'Database offline.'), null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
next(null, existingDevice, existingClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//jshint +W074
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function needs to be called with all server requests. It checks the user is currently logged in, esures the hmac is OK,
|
||||||
|
* and if so, it returns their client and device details. It requires multiple parameters:
|
||||||
|
*
|
||||||
|
* @type {function} validSession
|
||||||
|
* @param {!object} res - Response object for returning information. This function will respond directly on error.
|
||||||
|
* @param {!string} DeviceToken - The token assigned to the device at registration.
|
||||||
|
* @param {!string} SessionToken - The token returned at login. Note that this is valid for ~5 minutes only.
|
||||||
|
* Calling this function extends the SessionToken's life.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
* @param {!function} next - Not optional and should contain the code to be subsequently executed.
|
||||||
|
*/
|
||||||
|
exports.validSession = function(res, DeviceToken, SessionToken, functionInfo, hmacData, next) {
|
||||||
|
/**
|
||||||
|
* First check the session.
|
||||||
|
*/
|
||||||
|
exports.validateCurrentSession(DeviceToken, SessionToken, function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
res.status(200).json({
|
||||||
|
code: ('' + err.code),
|
||||||
|
info: err.message
|
||||||
|
});
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
err.message,
|
||||||
|
functionInfo.name,
|
||||||
|
err.code,
|
||||||
|
('AF [SessionToken ' + SessionToken + ' (DeviceToken ' + DeviceToken + ')]'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
/**
|
||||||
|
* Call back passing the error.
|
||||||
|
*/
|
||||||
|
next(err, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the hmacData to store the ClientName from the existingClient as that
|
||||||
|
* is required for the HMAC generation and validation
|
||||||
|
*/
|
||||||
|
hmacData.ClientName = existingClient.ClientName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the HMAC is fine.
|
||||||
|
*/
|
||||||
|
exports.checkHMAC(existingDevice, hmacData, functionInfo.name, function(err) {
|
||||||
|
if (err) {
|
||||||
|
res.status(200).json({
|
||||||
|
code: ('' + err.code),
|
||||||
|
info: err.message
|
||||||
|
});
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
err.message,
|
||||||
|
functionInfo.name,
|
||||||
|
err.code,
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
/**
|
||||||
|
* Call back passing the error.
|
||||||
|
*/
|
||||||
|
next(err, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All valid. Proceed.
|
||||||
|
*/
|
||||||
|
next(null, existingDevice, existingClient);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the PIN received from the device. This function will return an error object as the
|
||||||
|
* first parameter if something went wrong.
|
||||||
|
*
|
||||||
|
* @type {function} checkDevicePIN
|
||||||
|
* @param {!string} deviceAuthorisation - SHA256 of PIN as received from device.
|
||||||
|
* @param {!object} existingDevice - Existing object in database.
|
||||||
|
* @param {!object} timestamp - Reference time when clock was pulled.
|
||||||
|
* @param {!function} next - Function to call when verification complete.
|
||||||
|
*/
|
||||||
|
exports.checkDevicePIN = function(deviceAuthorisation, existingDevice, timestamp, next) {
|
||||||
|
/**
|
||||||
|
* Check for a locked device.
|
||||||
|
*/
|
||||||
|
if (existingDevice.LoginAttempts >= utils.PINLockout) {
|
||||||
|
next(utils.createError(399, 'Device locked. Please use PIN Reset.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split up the existing PIN and update if necessary.
|
||||||
|
*/
|
||||||
|
var receivedDeviceAuth;
|
||||||
|
var databaseDeviceAuth;
|
||||||
|
var authArray = existingDevice.DeviceAuthorisation.split('::');
|
||||||
|
async.series([
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Find the salt or create a new one if one doesn't exist.
|
||||||
|
*/
|
||||||
|
if (authArray[0] === '2') {
|
||||||
|
/**
|
||||||
|
* PIN encrypted using PBKDF2.
|
||||||
|
*/
|
||||||
|
crypto.pbkdf2(deviceAuthorisation, existingDevice.DeviceSalt,
|
||||||
|
config.encryptPBKDF2Rounds, config.encryptPBKDF2Bytes, config.encryptPBKDF2Protocol,
|
||||||
|
function(err, newHash) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Update the database.
|
||||||
|
*/
|
||||||
|
receivedDeviceAuth = newHash.toString('hex');
|
||||||
|
databaseDeviceAuth = authArray[1];
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Problem with the encryption string.
|
||||||
|
*/
|
||||||
|
callback('Unknown encryption type.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* Final clause which is executed after everything else or when an error is detected.
|
||||||
|
*/
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(400, ('Error when checking PIN: ' + err)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the PIN matches.
|
||||||
|
*/
|
||||||
|
if (receivedDeviceAuth !== databaseDeviceAuth) {
|
||||||
|
/**
|
||||||
|
* Wrong PIN. Increase the fail count.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: existingDevice.DeviceToken}, {
|
||||||
|
$set: {LastUpdate: timestamp},
|
||||||
|
$inc: {LoginAttempts: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(401, 'Database offline.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for maximum number of retries; if so, lock the account.
|
||||||
|
*/
|
||||||
|
if (existingDevice.LoginAttempts === (utils.PINLockout - 1)) {
|
||||||
|
/**
|
||||||
|
* Send warning e-mail.
|
||||||
|
*/
|
||||||
|
const suspendUrl = formattingUtils.formatPortalUrl('personal/devices');
|
||||||
|
var htmlEmail = templates.render('device-locked', {
|
||||||
|
DeviceNumber: existingDevice.DeviceNumber,
|
||||||
|
suspendDeviceUrl: suspendUrl
|
||||||
|
});
|
||||||
|
mailer.sendEmailByID(null, existingDevice.ClientID, 'Bridge Device Locked', htmlEmail, 'auth.checkDevicePIN',
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(402, 'Unable to send e-mail.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the user that the device has been locked.
|
||||||
|
*/
|
||||||
|
next(utils.createError(403, 'Wrong PIN. ' + utils.PINLockout +
|
||||||
|
' failed attempts have locked this device.'));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrong PIN - more attempts left.
|
||||||
|
*/
|
||||||
|
next(utils.createError(404, 'Wrong PIN.'));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PIN matched successfully.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: existingDevice.DeviceToken}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
LoginAttempts: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(405, 'Database offline.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
next(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the client password. This function will return an error object as the first parameter if something went wrong.
|
||||||
|
*
|
||||||
|
* @type {function} checkClientPassword
|
||||||
|
* @param {!string} password - SHA256 of password as received from device.
|
||||||
|
* @param {!object} existingClient - Existing object in database.
|
||||||
|
* @param {!object} timestamp - Reference time when clock was pulled.
|
||||||
|
* @param {!function} next - Function to call when verification complete.
|
||||||
|
*/
|
||||||
|
exports.checkClientPassword = function(password, existingClient, timestamp, next) {
|
||||||
|
/**
|
||||||
|
* Check for a locked account.
|
||||||
|
*/
|
||||||
|
if (existingClient.LoginAttempts >= utils.passwordLockout) {
|
||||||
|
next(utils.createError(406, 'Attempted login to locked account. Please contact Comcarde.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split up the existing password and update if necessary.
|
||||||
|
*/
|
||||||
|
var receivedPassword;
|
||||||
|
var databasePassword;
|
||||||
|
var passArray = existingClient.Password.split('::');
|
||||||
|
async.series([
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Find the salt or create a new one if one doesn't exist.
|
||||||
|
*/
|
||||||
|
if (passArray[0] === '2') {
|
||||||
|
/**
|
||||||
|
* Password encrypted using PBKDF2.
|
||||||
|
*/
|
||||||
|
crypto.pbkdf2(password, existingClient.ClientSalt,
|
||||||
|
config.encryptPBKDF2Rounds, config.encryptPBKDF2Bytes, config.encryptPBKDF2Protocol,
|
||||||
|
function(err, newHash) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Update the database.
|
||||||
|
*/
|
||||||
|
receivedPassword = newHash.toString('hex');
|
||||||
|
databasePassword = passArray[1];
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Problem with the encryption string.
|
||||||
|
*/
|
||||||
|
callback('Unknown encryption type.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* Final clause which is executed after everything else or when an error is detected.
|
||||||
|
*/
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(407, ('Error when checking password: ' + err)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the password matches.
|
||||||
|
*/
|
||||||
|
if (receivedPassword !== databasePassword) {
|
||||||
|
/**
|
||||||
|
* Wrong password. Increase the fail count.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {ClientID: existingClient.ClientID}, {
|
||||||
|
$set: {LastUpdate: timestamp},
|
||||||
|
$inc: {LoginAttempts: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(408, 'Database offline.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for maximum number of retries; if so, lock the account.
|
||||||
|
*/
|
||||||
|
if (existingClient.LoginAttempts === (utils.passwordLockout - 1)) {
|
||||||
|
/**
|
||||||
|
* Send warning e-mail.
|
||||||
|
*/
|
||||||
|
var htmlEmail = templates.render('account-locked', {
|
||||||
|
ClientName: existingClient.ClientName
|
||||||
|
});
|
||||||
|
mailer.sendEmail(null, existingClient.ClientName, 'Bridge Account Locked',
|
||||||
|
htmlEmail, 'auth.checkClientPassword',
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(409, 'Unable to send e-mail.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the user that the client account has been locked.
|
||||||
|
*/
|
||||||
|
next(utils.createError(410, 'Wrong password. ' + utils.passwordLockout +
|
||||||
|
' failed attempts have locked the client account.'));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrong password - more attempts left.
|
||||||
|
*/
|
||||||
|
next(utils.createError(411, 'Wrong password.'));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password matched successfully. Reset login attempts.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {ClientID: existingClient.ClientID}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
LoginAttempts: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(412, 'Database offline.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
next(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new salt and encrypts the password hash using PBKDF2.
|
||||||
|
* This function will return an error object as the first parameter if something went wrong.
|
||||||
|
*
|
||||||
|
* @type {function} encryptPBKDF2
|
||||||
|
* @param {!string} input - SHA256 of input to be encrypted using PBKDF2.
|
||||||
|
* @param {!function} next - Function to call when verification complete.
|
||||||
|
* @param {!object} next.err - Error object. null on success.
|
||||||
|
* @param {!string} next.newSalt - Random salt for encoding.
|
||||||
|
* @param {!string} next.newHash - Hashed input using new salt.
|
||||||
|
*/
|
||||||
|
exports.encryptPBKDF2 = function(input, next) {
|
||||||
|
/**
|
||||||
|
* Create a new salt.
|
||||||
|
*/
|
||||||
|
crypto.randomBytes(config.encryptPBKDF2Bytes, function(err, salt) {
|
||||||
|
if (err) {
|
||||||
|
next(err, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Success. Encrypt the password.
|
||||||
|
*/
|
||||||
|
var newSalt = salt.toString('hex');
|
||||||
|
crypto.pbkdf2(input, newSalt, config.encryptPBKDF2Rounds, config.encryptPBKDF2Bytes, config.encryptPBKDF2Protocol,
|
||||||
|
function(err, hash) {
|
||||||
|
if (err) {
|
||||||
|
next(err, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All done. Convert the hash and call back with the new values.
|
||||||
|
*/
|
||||||
|
var newHash = hash.toString('hex');
|
||||||
|
next(null, newSalt, newHash);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reponds with an HTML page.
|
||||||
|
*
|
||||||
|
* @type {function} respond
|
||||||
|
* @param {!object} res - response object. End will be called by this function.
|
||||||
|
* @param {!int} responseCode - HTML response code.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {!string} code - The code associated with the response, e.g. '235', '10014'.
|
||||||
|
* @param {!string} fileName - Name of the Pug file that is the HTML source: e.g. 'templates/39_expired_token.pug'.
|
||||||
|
* @param {!object} data - Parameters used to render the HTML file.
|
||||||
|
* @param {!string} logType - The log entry type to be recorded - e.g. 'INFO', 'WARNING' etc.
|
||||||
|
* Omit if no system log entry is needed such as in 'Database offline'.
|
||||||
|
* @param {!string} infoString - If logType above is present, infoString is will be used as the information to be logged.
|
||||||
|
* @param {!string} altUser - If logType above is present, altUser can be used to log different user name. If not, 'UU' wil be used.
|
||||||
|
*/
|
||||||
|
exports.respondHTML = function(res, responseCode, functionInfo, code, fileName, data, logType, infoString, altUser) {
|
||||||
|
/**
|
||||||
|
* Respond to the request.
|
||||||
|
*/
|
||||||
|
var toReturn = templates.render(fileName, data);
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||||
|
res.end(toReturn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log what has happened if required.
|
||||||
|
*/
|
||||||
|
var logUser = '';
|
||||||
|
if (altUser) {
|
||||||
|
logUser = altUser;
|
||||||
|
} else {
|
||||||
|
logUser = 'UU';
|
||||||
|
}
|
||||||
|
if (logType) {
|
||||||
|
log.system(
|
||||||
|
logType,
|
||||||
|
infoString,
|
||||||
|
functionInfo.name,
|
||||||
|
code,
|
||||||
|
logUser,
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add HTML page generation details regardless.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'PAGE',
|
||||||
|
('Generated file returned [' + fileName + '].'),
|
||||||
|
functionInfo.name,
|
||||||
|
code,
|
||||||
|
logUser,
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses a passed HMAC key to generate the data packet.
|
||||||
|
*
|
||||||
|
* @type {function} respond
|
||||||
|
* @param {!object} res - response object. End will be called by this function.
|
||||||
|
* @param {!int} responseCode - HTML response code.
|
||||||
|
* @param {!object} existingDevice - Existing object in database. If set to null the function works as a normal res.status() call.
|
||||||
|
* @param {!object} hmacData - hmac information {!address, !method, !body, !ClientName, ?timestamp, ?hmac}
|
||||||
|
* This function can be used to respond to non hmac calls by adding null in here.
|
||||||
|
* Typically set existingDevice and hmacData to null together.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {!object} data - Body of the packet as a JSON object. Note that 'info' and 'code' must be present.
|
||||||
|
* @param {!string} logType - The log entry type to be recorded - e.g. 'INFO', 'WARNING' etc.
|
||||||
|
* Omit if no system log entry is needed such as in 'Database offline'.
|
||||||
|
* @param {!string} altString - If logType above is present, altString can be used to log different data than the user receives.
|
||||||
|
* @param {!string} altUser - If logType above is present, altUser can be used to log different user name. If not, 'UU' wil be used.
|
||||||
|
* This parameter is ignored if hmacData and existingDevice are present.
|
||||||
|
*
|
||||||
|
* Cyclomatic complexity error disabled as it seems unnecessary.
|
||||||
|
*/
|
||||||
|
// jshint -W074
|
||||||
|
exports.respond = function(res, responseCode, existingDevice, hmacData, functionInfo, data, logType, altString, altUser) {
|
||||||
|
/**
|
||||||
|
* Ensure that the function is getting the correct data to process.
|
||||||
|
* Checking disabled for speed outwith the development environment.
|
||||||
|
*/
|
||||||
|
if (config.isDevEnv) {
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new Error('auth.respond received bad data from ' + functionInfo.name + ': data is not an object.');
|
||||||
|
}
|
||||||
|
if (!('code' in data)) {
|
||||||
|
throw new Error('auth.respond received bad data from ' + functionInfo.name + ': data is missing code field.');
|
||||||
|
}
|
||||||
|
if (!('info' in data)) {
|
||||||
|
throw new Error('auth.respond received bad data from ' + functionInfo.name + ': data is missing info field.');
|
||||||
|
}
|
||||||
|
if (typeof data.code !== 'string') {
|
||||||
|
throw new Error('auth.respond received bad data from ' + functionInfo.name + ': data.code is not a string.');
|
||||||
|
}
|
||||||
|
if (typeof data.info !== 'string') {
|
||||||
|
throw new Error('auth.respond received bad data from ' + functionInfo.name + ': data.info is not a string.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up variables for an HMAC based return.
|
||||||
|
*/
|
||||||
|
var key = '';
|
||||||
|
var DeviceUuid = '';
|
||||||
|
if (existingDevice) {
|
||||||
|
if (functionInfo.name === 'RotateHMAC.process') {
|
||||||
|
key = existingDevice.PendingHMAC;
|
||||||
|
DeviceUuid = existingDevice.DeviceUuid;
|
||||||
|
} else {
|
||||||
|
key = existingDevice.CurrentHMAC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If no key is available then there is no HMAC. We do not need to sign.
|
||||||
|
*/
|
||||||
|
if ((hmacData === null) || (key === '')) {
|
||||||
|
/**
|
||||||
|
* Respond to the request.
|
||||||
|
*/
|
||||||
|
res.status(responseCode).json(data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log what has happened if required.
|
||||||
|
*/
|
||||||
|
if (logType) {
|
||||||
|
var logString = '';
|
||||||
|
var logUser = '';
|
||||||
|
if (altString) {
|
||||||
|
logString = altString;
|
||||||
|
} else {
|
||||||
|
logString = data.info;
|
||||||
|
}
|
||||||
|
if (altUser) {
|
||||||
|
logUser = altUser;
|
||||||
|
} else {
|
||||||
|
logUser = 'UU';
|
||||||
|
}
|
||||||
|
log.system(
|
||||||
|
logType,
|
||||||
|
logString,
|
||||||
|
functionInfo.name,
|
||||||
|
data.code,
|
||||||
|
logUser,
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session token exception for Login1 due to the signing token not yet being saved.
|
||||||
|
*/
|
||||||
|
var sessionToken = '';
|
||||||
|
if (functionInfo.name === 'Login1.process') {
|
||||||
|
sessionToken = data.SessionToken;
|
||||||
|
} else {
|
||||||
|
sessionToken = existingDevice.SessionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the data and create the hmac.
|
||||||
|
*/
|
||||||
|
var timestamp = moment().utc().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z';
|
||||||
|
var text = JSON.stringify(data);
|
||||||
|
var fullText = hmacData.address + hmacData.method + timestamp + hmacData.ClientName + sessionToken + DeviceUuid + text;
|
||||||
|
var newkey = new Buffer(key, 'hex'); // Re-encode the key for use with the hmac.
|
||||||
|
var hmac = crypto.createHmac('sha256', newkey); // Create the HMAC object.
|
||||||
|
hmac.setEncoding('hex'); // Set encoding.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that the callback is attached as listener to stream's finish event.
|
||||||
|
*/
|
||||||
|
hmac.end(fullText, function() {
|
||||||
|
/**
|
||||||
|
* Read the HMAC and respond.
|
||||||
|
*/
|
||||||
|
var hash = hmac.read();
|
||||||
|
res.writeHead(responseCode, {
|
||||||
|
'bridge-hmac': hash,
|
||||||
|
'bridge-timestamp': timestamp,
|
||||||
|
'Content-Type': 'application/json; charset=utf-8' // Return that this is JSON
|
||||||
|
});
|
||||||
|
res.end(text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log what has happened if required.
|
||||||
|
*/
|
||||||
|
if (logType) {
|
||||||
|
var logString = '';
|
||||||
|
if (altString) {
|
||||||
|
logString = altString;
|
||||||
|
} else {
|
||||||
|
logString = data.info;
|
||||||
|
}
|
||||||
|
log.system(
|
||||||
|
logType,
|
||||||
|
logString,
|
||||||
|
functionInfo.name,
|
||||||
|
data.code,
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// jshint +W074
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks an incoming HMAC.
|
||||||
|
*
|
||||||
|
* @type {function} checkHMAC
|
||||||
|
* @param {!object} existingDevice - Existing object in database.
|
||||||
|
* @param {!object} hmacData - hmac information {!address, !method, !body, !ClientName, ?timestamp, ?hmac}
|
||||||
|
* @param {!string} functionName - The function that called the validation process: e.g. 'PayCodeRequest.process'.
|
||||||
|
* @param {!function} next - Function that should be called once processing is complete.
|
||||||
|
*
|
||||||
|
* Cyclomatic complexity error disabled as it seems unnecessary.
|
||||||
|
*/
|
||||||
|
// jshint -W074
|
||||||
|
exports.checkHMAC = function(existingDevice, hmacData, functionName, next) {
|
||||||
|
/**
|
||||||
|
* Check for HMAC problems first.
|
||||||
|
*/
|
||||||
|
if (existingDevice.HMACAttempts >= config.maxHMACAttempts) {
|
||||||
|
next(utils.createError(458, 'HMAC error: too many failed HMAC attempts.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only Login1 and RotateHMAC are valid calls if there is a PendingHMAC.
|
||||||
|
*/
|
||||||
|
if (existingDevice.PendingHMAC !== '') {
|
||||||
|
if ((functionName !== 'RotateHMAC.process') && (functionName !== 'Login1.process')) {
|
||||||
|
next(utils.createError(459, 'HMAC error: HMAC must be rotated using RotateHMAC.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = '';
|
||||||
|
var DeviceUuid = '';
|
||||||
|
if (functionName === 'RotateHMAC.process') {
|
||||||
|
key = existingDevice.PendingHMAC;
|
||||||
|
DeviceUuid = existingDevice.DeviceUuid;
|
||||||
|
} else {
|
||||||
|
key = existingDevice.CurrentHMAC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the HMAC key is blank then no HMAC has been issued - re-register the device.
|
||||||
|
* Note there is one exception and that is on Login1 where there is a PendingHMAC.
|
||||||
|
*/
|
||||||
|
if (key === '') {
|
||||||
|
if ((functionName === 'Login1.process') && (existingDevice.PendingHMAC !== '')) {
|
||||||
|
next(null);
|
||||||
|
} else {
|
||||||
|
next(utils.createError(462, 'HMAC error: No valid HMAC key - please re-register the device.'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for timestamp errors.
|
||||||
|
*/
|
||||||
|
var output = '';
|
||||||
|
if (!('timestamp' in hmacData)) {
|
||||||
|
next(utils.createError(446, 'HMAC error: \"bridge-timestamp\" not present.'));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
output = valid.validateFieldTimeStamp(hmacData.timestamp);
|
||||||
|
if (output) {
|
||||||
|
next(utils.createError(449, output));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for desync.
|
||||||
|
*/
|
||||||
|
var upperTimestamp = new Date();
|
||||||
|
upperTimestamp.setSeconds(upperTimestamp.getSeconds() + config.HMACDesyncThreshold);
|
||||||
|
if (hmacData.timestamp > upperTimestamp) {
|
||||||
|
next(utils.createError(451, 'HMAC error: timestamp is in the future.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var lowerTimestamp = new Date();
|
||||||
|
lowerTimestamp.setSeconds(lowerTimestamp.getSeconds() - config.HMACDesyncThreshold);
|
||||||
|
if (lowerTimestamp > hmacData.timestamp) {
|
||||||
|
next(utils.createError(452, 'HMAC error: timestamp has expired.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for hmac errors.
|
||||||
|
*/
|
||||||
|
if (!('hmac' in hmacData)) {
|
||||||
|
next(utils.createError(447, 'HMAC error: \"bridge-hmac\" not present.'));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
output = valid.validateFieldHMAC(hmacData.hmac);
|
||||||
|
if (output) {
|
||||||
|
next(utils.createError(450, output));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble the HMAC.
|
||||||
|
*/
|
||||||
|
var fullText = hmacData.address + hmacData.method + hmacData.timestamp + hmacData.ClientName + DeviceUuid + hmacData.body;
|
||||||
|
var newkey = new Buffer(key, 'hex'); // Re-encode the key for use with the hmac.
|
||||||
|
var hmac = crypto.createHmac('sha256', newkey); // Create the HMAC object.
|
||||||
|
hmac.setEncoding('hex'); // Set encoding.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that the callback is attached as listener to stream's finish event.
|
||||||
|
*/
|
||||||
|
hmac.end(fullText, function() {
|
||||||
|
/**
|
||||||
|
* Read the HMAC and respond.
|
||||||
|
*/
|
||||||
|
var hash = hmac.read();
|
||||||
|
if (hash !== hmacData.hmac) {
|
||||||
|
/**
|
||||||
|
* HMAC error. Tick up HMAC attempts or bar the device if there have been too many problems.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var toUpdate = {
|
||||||
|
$set: {LastUpdate: timestamp},
|
||||||
|
$inc: {HMACAttempts: 1}
|
||||||
|
};
|
||||||
|
if (existingDevice.HMACAttempts >= (config.maxHMACAttempts - 1)) {
|
||||||
|
toUpdate.$bit = {DeviceStatus: {or: utils.DeviceBarredMask}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write this information to the correct device.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: existingDevice.DeviceToken}, toUpdate,
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
next(utils.createError(460, ('HMAC error: database offline.')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the device of the error.
|
||||||
|
*/
|
||||||
|
if (existingDevice.HMACAttempts >= (config.maxHMACAttempts - 1)) {
|
||||||
|
next(utils.createError(461, ('HMAC error: security check failed and device barred.')));
|
||||||
|
} else {
|
||||||
|
next(utils.createError(448, ('HMAC error: security check failed.')));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// jshint +W074
|
262
node_server/ComServe/config.js
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
/* eslint-disable no-process-env */
|
||||||
|
/* eslint-disable no-process-exit */
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
/**
|
||||||
|
* @fileOverview Node.js Bridge Server Config for Bridge Pay
|
||||||
|
* @preserve Copyright 2014-2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*/
|
||||||
|
/* eslint-disable no-process-env, no-process-exit, no-console */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes needed for this module.
|
||||||
|
*/
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const path = require('path');
|
||||||
|
const exitCodes = require('../exitcodes.js');
|
||||||
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version information.
|
||||||
|
*/
|
||||||
|
exports.EULAVersion = '1.0';
|
||||||
|
exports.CCServerReleaseType = 'Beta'; // Options include Alpha, Beta etc.
|
||||||
|
exports.useHTTPS = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure NODE_ENV is either 'development' or 'production'.
|
||||||
|
*/
|
||||||
|
exports.isDevEnv = (process.env.NODE_ENV === 'development');
|
||||||
|
exports.isProdEnv = (process.env.NODE_ENV === 'production');
|
||||||
|
if (!exports.isDevEnv && !exports.isProdEnv) {
|
||||||
|
console.log('NODE_ENV environment variable missing or invalid: must be either \'development\' or \'production\'.');
|
||||||
|
process.exit(exitCodes.EXIT_CODE_NO_NODE_ENV);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the acquisition server is either 'test' or 'live'.
|
||||||
|
*/
|
||||||
|
exports.isTestEnv = (process.env.acquirer === 'test');
|
||||||
|
exports.isLiveEnv = (process.env.acquirer === 'live');
|
||||||
|
if (!exports.isTestEnv && !exports.isLiveEnv) {
|
||||||
|
console.log('Acquisition server environment variable missing or invalid: must be either \'test\' or \'live\'.');
|
||||||
|
process.exit(exitCodes.EXIT_CODE_NO_ACQUISITION_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the appropriate environment variable is present and sets the variable if so.
|
||||||
|
* Error constants. These need to be in config as other code depends on the environment variables being loaded first.
|
||||||
|
*
|
||||||
|
* @param {!string} keyName - The key to check for.
|
||||||
|
* @param {!string} destName - The key to be written in exports.
|
||||||
|
* @param {varies} [defaultValue] - Default value if one is not provided in the environment
|
||||||
|
*/
|
||||||
|
exports.readENVVariable = function(keyName, destName, defaultValue) {
|
||||||
|
let value = null;
|
||||||
|
|
||||||
|
if (process.env.hasOwnProperty(keyName)) {
|
||||||
|
value = process.env[keyName];
|
||||||
|
} else if (defaultValue === undefined) {
|
||||||
|
console.log('Missing environment variable: ' + keyName + '\nPlease set the variables to launch the server.');
|
||||||
|
process.exit(exitCodes.EXIT_CODE_NO_ENVIRONMENT_VARS);
|
||||||
|
} else {
|
||||||
|
value = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the system variable if it has not been set. Do not overwrite.
|
||||||
|
*/
|
||||||
|
if (!(destName in exports)) {
|
||||||
|
exports[destName] = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read in all the environment variables.
|
||||||
|
* First key is the environment varable, the second variable is the export name.
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('AESKey', 'AESKey'); // e.g. kJq5fW4m/lLG6oLTcM+fPFmlHL9FU9=N
|
||||||
|
exports.readENVVariable('ServerCommit', 'ServerCommit'); // e.g. 7d33f90
|
||||||
|
exports.readENVVariable('PortalCommit', 'PortalCommit'); // e.g. 7d33f90
|
||||||
|
exports.readENVVariable('uuid', 'CCUUID'); // e.g. 2478b89c-c165-445b-9ff6-eb0beec60d12
|
||||||
|
exports.readENVVariable('HOSTNAME', 'CCServerName'); // e.g. Virtual machine name or ID
|
||||||
|
exports.readENVVariable('sgroup_name', 'CCServerGroup'); // e.g. Name of the group deployment.
|
||||||
|
exports.readENVVariable('loadbalancer_vip', 'CCServerIP'); // e.g. 172.31.0.2
|
||||||
|
exports.readENVVariable('webAddress', 'CCWebsiteAddress'); // e.g. dev.bridgepay.uk
|
||||||
|
exports.readENVVariable('forceHTTPS', 'forceHTTPS'); // e.g. 'true' or 'false'
|
||||||
|
if (exports.forceHTTPS === 'false') {
|
||||||
|
exports.useHTTPS = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Service
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('serverHttpPort', 'serverHttpPort', 80);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption keys.
|
||||||
|
*/
|
||||||
|
exports.hashedAESKey = crypto.createHash('sha256').update(exports.AESKey).digest('hex'); // Hashed version of the above key.
|
||||||
|
exports.pinCryptoVersion = '2';
|
||||||
|
exports.passwordCryptoVersion = '2';
|
||||||
|
exports.encryptPBKDF2Rounds = 10000;
|
||||||
|
exports.encryptPBKDF2Bytes = 32;
|
||||||
|
exports.encryptPBKDF2Protocol = 'sha256';
|
||||||
|
exports.HMACBytes = 32;
|
||||||
|
exports.HMACDesyncThreshold = 120; // Seconds.
|
||||||
|
exports.maxHMACAttempts = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payments Setup.
|
||||||
|
* The verification provider can either be Worldpay or Credorax.
|
||||||
|
* Optionally, a card can be added with zero checking if 'None' is used.
|
||||||
|
* To shut down verification completely please use a blank string ''.
|
||||||
|
*/
|
||||||
|
exports.verificationProvider = 'Worldpay';
|
||||||
|
exports.demoCardPAN = '4917610000000000003';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credorax setup.
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('credoraxKey', 'comcardeCipherKey');
|
||||||
|
exports.readENVVariable('credoraxMerchantID', 'comcardeMerchantID');
|
||||||
|
if (exports.isTestEnv) {
|
||||||
|
exports.credoraxPrimaryGateway = 'https://intconsole.credorax.com/intenv/service/gateway'; // Normal gateway.
|
||||||
|
exports.credoraxSecondaryGateway = 'https://intconsole.credorax.com/intenv/service/gateway'; // Failover gateway.
|
||||||
|
} else {
|
||||||
|
exports.credoraxPrimaryGateway = 'https://comcarde-eu1.gate.credorax.net/crax_gate/service/gateway'; // Normal gateway.
|
||||||
|
exports.credoraxSecondaryGateway = 'https://comcarde-na1.gate.credorax.net/crax_gate/service/gateway'; // Failover gateway.
|
||||||
|
}
|
||||||
|
exports.credoraxCurrentGateway = exports.credoraxPrimaryGateway;
|
||||||
|
exports.credoraxChangeoverThreshold = 3; // Number of failed transactions before switchover.
|
||||||
|
exports.credoraxChangeRate = 1; // Number of failed comms reduced every 15 minutes.
|
||||||
|
exports.credoraxPrimaryGatewayFailure = 'https://' + exports.CCWebsiteAddress +
|
||||||
|
': The primary Credorax gateway has failed. Secondary is now active.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worldpay setup.
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('worldpayMerchantID', 'worldpayMerchantID');
|
||||||
|
exports.readENVVariable('worldpayServiceKey', 'worldpayServiceKey');
|
||||||
|
exports.readENVVariable('worldpayClientKey', 'worldpayClientKey');
|
||||||
|
exports.worldpayPrimaryGateway = 'https://api.worldpay.com/v1/'; // Normal and test gateways are the same.
|
||||||
|
exports.worldpayNotificationThreshold = 3; // Number of failed transactions before notification.
|
||||||
|
exports.worldpayChangeRate = 1; // Number of failed comms reduced every 15 minutes.
|
||||||
|
exports.worldpayPrimaryGatewayFailure = 'https://' + exports.CCWebsiteAddress + ': The primary Worldpay gateway has failed.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database configuration.
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('mongoUser', 'mongoUser');
|
||||||
|
exports.readENVVariable('mongoPassword', 'mongoPassword');
|
||||||
|
exports.readENVVariable('mongoDBAddress', 'mongoDBAddress');
|
||||||
|
exports.readENVVariable('mongoUseSSL', 'mongoUseSSL', 'true'); // Env vars are always strings.
|
||||||
|
exports.mongoUseSSL = (exports.mongoUseSSL !== 'false'); // Coerce to a bool as env is all strings.
|
||||||
|
if (exports.mongoUseSSL) {
|
||||||
|
exports.readENVVariable('mongoCACertBase64', 'mongoCACertBase64');
|
||||||
|
exports.mongoCA = [Buffer.from(exports.mongoCACertBase64, 'base64')];
|
||||||
|
}
|
||||||
|
exports.externaldbAddress = 'mongodb://' + exports.mongoUser + ':' + exports.mongoPassword +
|
||||||
|
exports.mongoDBAddress + '/MDB?connectTimeoutMS=5000&authMechanism=SCRAM-SHA-1&authSource=MDB';
|
||||||
|
if (exports.mongoUseSSL) {
|
||||||
|
exports.externaldbAddress += '&ssl=true';
|
||||||
|
}
|
||||||
|
exports.internaldbAddress = exports.externaldbAddress; // Currently one and the same as there is no internal route.
|
||||||
|
exports.databaseUpdate = false; // Automatically updates the database to the latest version.
|
||||||
|
exports.databaseUpdateWrite = true; // Normally true. When false, updates are not actually written ot the database. For testing.
|
||||||
|
exports.databaseIntegrityCheck = true; // Integrity checking is an option only if databaseUpdate is enabled.
|
||||||
|
exports.databaseArchiveTransactions = true; // Moves incomplete transactions to the TransactionArchive.
|
||||||
|
exports.databaseArchiveAccounts = true; // Removes deleted accounts that never made any transactions.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selfie location.
|
||||||
|
*/
|
||||||
|
exports.defaultSelfie = 'defaultSelfie';
|
||||||
|
exports.defaultCompanyLogo0 = 'defaultCompanyLogo0';
|
||||||
|
exports.defaultSelfieData = '';
|
||||||
|
exports.defaultCompanyLogo0Data = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File system configuration.
|
||||||
|
*/
|
||||||
|
exports.temporaryDirectory = path.normalize(global.rootPath + 'temp/'); // Default swap directory.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LexisNexis Tracesmart IDU-AML values
|
||||||
|
* @see https://developer.tracesmart.co.uk/idu-aml
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('tracesmartIduAmlUrl', 'tracesmartIduAmlUrl');
|
||||||
|
exports.readENVVariable('tracesmartIduAmlUsername', 'tracesmartIduAmlUsername');
|
||||||
|
exports.readENVVariable('tracesmartIduAmlPassword', 'tracesmartIduAmlPassword');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ideal Postcodes API
|
||||||
|
* @see https://ideal-postcodes.co.uk/
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('idealPostcodesKey', 'idealPostcodesKey');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General Defines.
|
||||||
|
*/
|
||||||
|
exports.maxAddresses = 25;
|
||||||
|
exports.maxRegTokenAttempts = 3;
|
||||||
|
exports.maxItems = 100;
|
||||||
|
exports.maxInvoiceNumberAttempts = 3;
|
||||||
|
exports.callTimeout = 30000; // Number of miliseconds that a server will hold a port open for before it is shut down.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Console host
|
||||||
|
* - Used for links in registration emails etc.
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('cookieSecret', 'cookieSecret');
|
||||||
|
exports.webconsole = {
|
||||||
|
host: exports.CCWebsiteAddress,
|
||||||
|
path: '/portal/',
|
||||||
|
cookieSecret: exports.cookieSecret
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate Limits for express-rate-limit.
|
||||||
|
* Note that the identifier for the "same" client depends on the type of call.
|
||||||
|
* It defaults to IP, but some of the APIs can be more specific based on session
|
||||||
|
* tokens or device ids.
|
||||||
|
* @see {@link https://github.com/nfriedly/express-rate-limit}
|
||||||
|
*/
|
||||||
|
exports.rateLimits = {
|
||||||
|
/* APIs */
|
||||||
|
api: {
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 min window
|
||||||
|
max: 900, // Allow 900 requests per 15 min (~10 / second avg)
|
||||||
|
delayAfter: 0, // Never delay responses, only succeed or fail
|
||||||
|
delayMs: 0 // Never delay responses, only succeed or fail
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Portal static files. */
|
||||||
|
portalStatic: {
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 min window
|
||||||
|
max: 900, // Allow 900 requests per 15 minute, then fail (429)
|
||||||
|
delayAfter: 25, // Slow down after the first 25
|
||||||
|
delayMs: 10 // Delay by (10ms * (requests-delayAfter)) per request
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Anything else - 404 page, selfies and other such things that haven't been removed */
|
||||||
|
fallback: {
|
||||||
|
windowMs: 1 * 60 * 60 * 1000, // 1 hour window
|
||||||
|
max: 100, // Allow 100 requests per hour, then fail (429)
|
||||||
|
delayAfter: 1, // Slow down after the first request
|
||||||
|
delayMs: 10 // Delay by 10ms * (requests-delayAfter) per request
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secret for creation and validation of JSON Web Tokens for the integrations API
|
||||||
|
* @see https://github.com/auth0/node-jsonwebtoken
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('integrationsTokenSecret', 'integrationsTokenSecret');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The commit hash that the server was built from.
|
||||||
|
*/
|
||||||
|
exports.readENVVariable('commitHash', 'commitHash');
|
||||||
|
exports.CCServerVersion = packageJson.version + '-' + exports.commitHash;
|
288
node_server/ComServe/credorax.js
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Credorax Acquiring Code
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Integrates with Credorax and interprets any results received.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var querystring = require('querystring');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var request = require('request');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var sms = require(global.pathPrefix + 'sms.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control defines.
|
||||||
|
*/
|
||||||
|
exports.useMCC6012 = 0; // Add [jx] fields. Support seems patchy.
|
||||||
|
exports.useAVS = 0; // Add [c4-c10] fields. Support seems patchy.
|
||||||
|
exports.credoraxPostData = 1; // Shows the info sent to Credorax.
|
||||||
|
exports.primaryFailedComms = 0; // Ticks up every time communications fail with Credorax's primary server.
|
||||||
|
exports.credoraxSMSAlertSent = 0; // Designed to prevent multiple SMS calls.
|
||||||
|
exports.credoraxTimeout = 25000; // Apps use 30 seconds as we need a little time to respond to them.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Appendix D errors so the system interprets a meaningful result.
|
||||||
|
*/
|
||||||
|
exports.processingResponseReason = [
|
||||||
|
{'00': 'Approved or completed successfully'},
|
||||||
|
{'01': 'Refer to card issuer'},
|
||||||
|
{'02': 'Refer to card issuer special condition'},
|
||||||
|
{'03': 'Invalid merchant'},
|
||||||
|
{'04': 'Pick up card'},
|
||||||
|
{'05': 'Do not Honour'},
|
||||||
|
{'06': 'Invalid Transaction for Terminal'},
|
||||||
|
{'07': 'Pick up card special condition'},
|
||||||
|
{'08': 'Time-Out'},
|
||||||
|
{'09': 'No Original'},
|
||||||
|
{'10': 'Approved for partial amount'},
|
||||||
|
{'11': 'Partial Approval'},
|
||||||
|
{'12': 'Invalid transaction card / issuer / acquirer'},
|
||||||
|
{'13': 'Invalid amount'},
|
||||||
|
{'14': 'Invalid card number'},
|
||||||
|
{'17': 'Invalid Capture date (terminal business date)'},
|
||||||
|
{'19': 'System Error; Re-enter transaction'},
|
||||||
|
{'20': 'No From Account'},
|
||||||
|
{'21': 'No To Account'},
|
||||||
|
{'22': 'No Checking Account'},
|
||||||
|
{'23': 'No Saving Account'},
|
||||||
|
{'24': 'No Credit Account'},
|
||||||
|
{'30': 'Format error'},
|
||||||
|
{'34': 'Implausible card data'},
|
||||||
|
{'39': 'Transaction Not Allowed'},
|
||||||
|
{'41': 'Lost Card, Pickup'},
|
||||||
|
{'42': 'Special Pickup'},
|
||||||
|
{'43': 'Hot Card, Pickup (if possible)'},
|
||||||
|
{'44': 'Pickup Card'},
|
||||||
|
{'51': 'Not sufficient funds'},
|
||||||
|
{'52': 'No checking Account'},
|
||||||
|
{'53': 'No savings account'},
|
||||||
|
{'54': 'Expired card'},
|
||||||
|
{'55': 'Pin incorrect'},
|
||||||
|
{'57': 'Transaction not allowed for cardholder'},
|
||||||
|
{'58': 'Transaction not allowed for merchant'},
|
||||||
|
{'59': 'Suspected Fraud'},
|
||||||
|
{'61': 'Exceeds withdrawal amount limit'},
|
||||||
|
{'62': 'Restricted card'},
|
||||||
|
{'63': 'MAC Key Error'},
|
||||||
|
{'65': 'Activity count limit exceeded'},
|
||||||
|
{'66': 'Exceeds Acquirer Limit'},
|
||||||
|
{'67': 'Retain Card; no reason specified'},
|
||||||
|
{'68': 'Response received too late.'},
|
||||||
|
{'75': 'Pin tries exceeded'},
|
||||||
|
{'76': 'Invalid Account'},
|
||||||
|
{'77': 'Issuer Does Not Participate In The Service'},
|
||||||
|
{'78': 'Function Not Available'},
|
||||||
|
{'79': 'Key Validation Error'},
|
||||||
|
{'80': 'Approval for Purchase Amount Only'},
|
||||||
|
{'81': 'Unable to Verify PIN'},
|
||||||
|
{'82': 'Time out at issuer system'},
|
||||||
|
{'83': 'Not declined (Valid for all zero amount transactions)'},
|
||||||
|
{'84': 'Invalid Life Cycle of transaction'},
|
||||||
|
{'85': 'Not declined'},
|
||||||
|
{'86': 'Cannot verify pin'},
|
||||||
|
{'87': 'Purchase amount only, no cashback allowed'},
|
||||||
|
{'88': 'MAC sync Error'},
|
||||||
|
{'89': 'Security Violation'},
|
||||||
|
{'91': 'Issuer not available'},
|
||||||
|
{'92': 'Unable to route at acquirer Module'},
|
||||||
|
{'93': 'Transaction cannot be completed'},
|
||||||
|
{'94': 'Duplicate transaction'},
|
||||||
|
{'95': 'Contact Acquirer'},
|
||||||
|
{'96': 'System malfunction'},
|
||||||
|
{'97': 'No Funds Transfer'},
|
||||||
|
{'98': 'Duplicate Reversal'},
|
||||||
|
{'99': 'Duplicate Transaction'},
|
||||||
|
{'N3': 'Cash Service Not Available'},
|
||||||
|
{'N4': 'Cash Back Request Exceeds Issuer Limit'},
|
||||||
|
{'N7': '(Visa) decline; CVV2 failure.'},
|
||||||
|
{'R0': 'Stop Payment Order'},
|
||||||
|
{'R1': 'Revocation of Authorisation Order'},
|
||||||
|
{'R3': 'Revocation of all Authorisations Order'}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credorax main API function call.
|
||||||
|
*
|
||||||
|
* @type {function} CredoraxFunction
|
||||||
|
* @param {!object} credorax - JSON stucture with filled in data that represents the call.
|
||||||
|
* @param {!object} M - Merchant ID.
|
||||||
|
* @param {!object} cipherKey - Key issued to acquiring merchant.
|
||||||
|
* @param {!function} callback - Call back that returns the result.
|
||||||
|
*/
|
||||||
|
exports.CredoraxFunction = function(credorax, M, cipherKey, callback) {
|
||||||
|
/**
|
||||||
|
* Local variables.
|
||||||
|
*/
|
||||||
|
var cryptoString = M;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse all keys.
|
||||||
|
*/
|
||||||
|
Object.keys(credorax).forEach(function(key) { cryptoString += credorax[key]; });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create hash.
|
||||||
|
*/
|
||||||
|
cryptoString += cipherKey;
|
||||||
|
var hash = crypto.createHash('MD5').update(cryptoString).digest('hex');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create query string.
|
||||||
|
*/
|
||||||
|
var postData = querystring.stringify({'K': hash, 'M': M});
|
||||||
|
postData += '&' + querystring.stringify(credorax);
|
||||||
|
postData = postData.replace('*', '%2A'); // stringify contains an error that does not process the asterisk correctly.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the data to be posted. Never post this information on the live server.
|
||||||
|
*/
|
||||||
|
if (exports.credoraxPostData && config.isDevEnv) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('[OUT] parameters: ' + JSON.stringify(postData)),
|
||||||
|
'credorax.CredoraxFunction',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the data by submitting it to Credorax.
|
||||||
|
*/
|
||||||
|
if (postData.length > 0) {
|
||||||
|
/**
|
||||||
|
* Set the headers
|
||||||
|
*/
|
||||||
|
var headers = {
|
||||||
|
'User-Agent': 'Super Agent/0.0.1',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
'Content-Length': postData.length
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Configure the request
|
||||||
|
*/
|
||||||
|
var options = {
|
||||||
|
url: config.credoraxCurrentGateway,
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
form: postData,
|
||||||
|
timeout: exports.credoraxTimeout
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the request.
|
||||||
|
*/
|
||||||
|
request(options, function(error, response, body) {
|
||||||
|
if (!error && response.statusCode === 200) {
|
||||||
|
/**
|
||||||
|
* A response was received. This does not mean the operation was successful.
|
||||||
|
*/
|
||||||
|
var credoraxResult = querystring.parse(body);
|
||||||
|
return callback(null, credoraxResult);
|
||||||
|
} else if (!error && response.statusCode !== 200) {
|
||||||
|
/**
|
||||||
|
* HTTP error.
|
||||||
|
*/
|
||||||
|
return callback('Error: HTTP Code: ' + response.statusCode);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Return a request error.
|
||||||
|
*/
|
||||||
|
return callback('Error: ' + error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* No data to post.
|
||||||
|
*/
|
||||||
|
return callback('No data to process.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credorax z6 code interpretation.
|
||||||
|
*
|
||||||
|
* @type {function} interpretZ6
|
||||||
|
* @param {!string} z6 - Error code.
|
||||||
|
* @return {!string} Interpreted error code or 'Unknown error code' if it is not found.
|
||||||
|
*/
|
||||||
|
exports.interpretZ6 = function(z6) {
|
||||||
|
var reason = 'Unknown error code';
|
||||||
|
if (exports.processingResponseReason.hasOwnProperty(z6)) {
|
||||||
|
reason = exports.processingResponseReason[z6];
|
||||||
|
}
|
||||||
|
return reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function deals with Credorax communication failures and gateway switchover if necessary.
|
||||||
|
*
|
||||||
|
* @type {function} commsFailure
|
||||||
|
* @param {!string} source - The function where the error was called from e.g. 'AddCard.process'.
|
||||||
|
*/
|
||||||
|
exports.commsFailure = function(source) {
|
||||||
|
/**
|
||||||
|
* General error - usually indicates Credorax is down.
|
||||||
|
* First check the current gateway and switch if necessary.
|
||||||
|
*/
|
||||||
|
if (config.credoraxCurrentGateway === config.credoraxPrimaryGateway) {
|
||||||
|
/**
|
||||||
|
* Still on primary gateway.
|
||||||
|
*/
|
||||||
|
if (exports.primaryFailedComms >= (config.credoraxChangeoverThreshold - 1)) {
|
||||||
|
/**
|
||||||
|
* Too many failures. Switching to secondary gateway.
|
||||||
|
*/
|
||||||
|
config.credoraxCurrentGateway = config.credoraxSecondaryGateway;
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
'Credorax primary gateway down. Moving to secondary gateway.',
|
||||||
|
source,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform admins.
|
||||||
|
*/
|
||||||
|
if (exports.credoraxSMSAlertSent === 0) {
|
||||||
|
/**
|
||||||
|
* Block multiple SMS messages and send a single one to the admin(s).
|
||||||
|
* Note that SMS is blocked before we know it has been sent; the callback structure means that this could
|
||||||
|
* be initialised hundreds of times on a loaded system before the first one returned.
|
||||||
|
*/
|
||||||
|
exports.credoraxSMSAlertSent = 1;
|
||||||
|
sms.sendSMS(null, (sms.adminMobile + ',' + sms.backupMobile),
|
||||||
|
config.credoraxPrimaryGatewayFailure, function(err, smsBalance) {
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'ERROR',
|
||||||
|
'Unable to send SMS.',
|
||||||
|
source,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('Credorax primary gateway failure. SMS sent to admins (SMS balance now ' + smsBalance + ').'),
|
||||||
|
source,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exports.primaryFailedComms += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
518
node_server/ComServe/hJSON.js
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
/* eslint-disable no-var, no-unused-vars, vars-on-top */
|
||||||
|
/* eslint-disable spaced-comment, global-require, lines-around-comment, comma-spacing */
|
||||||
|
/* eslint-disable no-use-before-define, no-useless-escape, brace-style, padded-blocks */
|
||||||
|
/* eslint-disable prefer-arrow-callback, promise/always-return, unicorn/catch-error-name */
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Comcarde Node.js JSON Handler
|
||||||
|
// Provides -Bridge- pay functionality.
|
||||||
|
// Copyright 2014-2015 Comcarde
|
||||||
|
// Written by Keith Symington
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Includes
|
||||||
|
var moment = require('moment');
|
||||||
|
var querystring = require('querystring');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var sms = require(global.pathPrefix + 'sms.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var credorax = require(global.pathPrefix + 'credorax.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var SendReport = require(global.pathPrefix + 'hJSON/SendReport');
|
||||||
|
var validator = require(global.pathPrefix + '../schemas/validator');
|
||||||
|
|
||||||
|
// Local variables.
|
||||||
|
exports.JSONServed = 0;
|
||||||
|
exports.showPackets = true; // Use to show the incoming packet detail. This is a debug mode.
|
||||||
|
exports.REST = 1;
|
||||||
|
exports.form = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of commands that are supported by this function. Commands added to
|
||||||
|
* this list MUST:
|
||||||
|
* 1. Have a matching handler in 'ComServe/hJSON/<name>.js''
|
||||||
|
* 2. Have a matching schema for the body in 'schemas/<name>.json'
|
||||||
|
* 3. Have the approprite errorcode added to the list
|
||||||
|
*
|
||||||
|
* WARNING: due to the change in direction, ALL commands are now unsupported!
|
||||||
|
* They have been moved to `UNSUPPORTED_COMMANDS` for easy reverting if needed.
|
||||||
|
*/
|
||||||
|
const SUPPORTED_COMMANDS = {};
|
||||||
|
|
||||||
|
const UNSUPPORTED_COMMANDS = {
|
||||||
|
/**
|
||||||
|
* Login and Authorisation Commands
|
||||||
|
*/
|
||||||
|
AcceptEULA: {validationErrorCode: 292},
|
||||||
|
Authorise2FARequest: {validationErrorCode: 455},
|
||||||
|
Get2FARequest: {validationErrorCode: 453},
|
||||||
|
KeepAlive: {validationErrorCode: 123},
|
||||||
|
LogOut1: {validationErrorCode: 123},
|
||||||
|
Login1: {validationErrorCode: 149},
|
||||||
|
PINReset: {validationErrorCode: 128},
|
||||||
|
RotateHMAC: {validationErrorCode: 444},
|
||||||
|
ElevateSession: {validationErrorCode: 558},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account Commands
|
||||||
|
*/
|
||||||
|
AddAddress: {validationErrorCode: 378},
|
||||||
|
AddCard: {validationErrorCode: 122},
|
||||||
|
ChangePIN: {validationErrorCode: 415},
|
||||||
|
ChangePassword: {validationErrorCode: 418},
|
||||||
|
DeleteAccount: {validationErrorCode: 151},
|
||||||
|
DeleteAddress: {validationErrorCode: 384},
|
||||||
|
GetTransactionDetail: {validationErrorCode: 190},
|
||||||
|
GetTransactionHistory: {validationErrorCode: 186},
|
||||||
|
ListAccounts: {validationErrorCode: 519},
|
||||||
|
ListDeletedAccounts: {
|
||||||
|
validationErrorCode: 519,
|
||||||
|
commandHandler: 'ListAccounts'
|
||||||
|
},
|
||||||
|
ListAddresses: {validationErrorCode: 376},
|
||||||
|
SetAccountAddress: {validationErrorCode: 391},
|
||||||
|
SetDefaultAccount: {validationErrorCode: 300},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Commands
|
||||||
|
*/
|
||||||
|
AddImage: {validationErrorCode: 217},
|
||||||
|
GetImage: {validationErrorCode: 220},
|
||||||
|
IconCache: {validationErrorCode: -1}, // Has no body, so can't fail validation
|
||||||
|
ImageCache: {validationErrorCode: 520},
|
||||||
|
ReportImage: {validationErrorCode: 223},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Commands
|
||||||
|
*/
|
||||||
|
ConfirmInvoice: {validationErrorCode: 493},
|
||||||
|
GetInvoice: {validationErrorCode: 513},
|
||||||
|
ListInvoices: {validationErrorCode: 511},
|
||||||
|
RejectInvoice: {validationErrorCode: 516},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merchant Commands
|
||||||
|
*/
|
||||||
|
ListItems: {validationErrorCode: 471},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message Commands
|
||||||
|
*/
|
||||||
|
DeleteMessage: {validationErrorCode: 485},
|
||||||
|
GetMessage: {validationErrorCode: 479},
|
||||||
|
ListMessages: {validationErrorCode: 477},
|
||||||
|
MarkMessage: {validationErrorCode: 482},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment Commands
|
||||||
|
*/
|
||||||
|
CancelPaymentRequest: {validationErrorCode: 161},
|
||||||
|
ConfirmTransaction: {validationErrorCode: 181},
|
||||||
|
GetTransactionUpdate: {validationErrorCode: 170},
|
||||||
|
PayCodeRequest: {validationErrorCode: 150},
|
||||||
|
RedeemPayCode: {validationErrorCode: 174},
|
||||||
|
RefundTransaction: {validationErrorCode: 227},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration Commands
|
||||||
|
*/
|
||||||
|
AddDevice: {validationErrorCode: 330},
|
||||||
|
DeleteDevice: {validationErrorCode: 363},
|
||||||
|
GetClientDetails: {validationErrorCode: 424},
|
||||||
|
ListDevices: {validationErrorCode: 361},
|
||||||
|
Register1: {validationErrorCode: 2},
|
||||||
|
Register2: {validationErrorCode: 124},
|
||||||
|
Register3: {validationErrorCode: 125},
|
||||||
|
Register4: {validationErrorCode: 126},
|
||||||
|
Register6: {validationErrorCode: 140},
|
||||||
|
Register7: {
|
||||||
|
validationErrorCode: 141,
|
||||||
|
paramsValidator: 'Register7.params'
|
||||||
|
},
|
||||||
|
Register8: {validationErrorCode: 142},
|
||||||
|
ResumeDevice: {validationErrorCode: 432},
|
||||||
|
SetClientDetails: {validationErrorCode: 422},
|
||||||
|
SetDeviceName: {validationErrorCode: 438},
|
||||||
|
SuspendDevice: {validationErrorCode: 427},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utils functions
|
||||||
|
*/
|
||||||
|
PostCodeLookup: {validationErrorCode: 530}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define where the schemas should be loaded from
|
||||||
|
*/
|
||||||
|
const SCHEMA_DIR = path.join(global.pathPrefix, '..', 'schemas');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define where the command handlers should be loaded from
|
||||||
|
*/
|
||||||
|
const COMMAND_HANDLER_DIR = path.join(global.pathPrefix, 'hJSON');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the command handlers that are loaded based on the SUPPORTED_COMMANDS
|
||||||
|
*/
|
||||||
|
var commandHandlers = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the command handlers base on the list of supported commands
|
||||||
|
*/
|
||||||
|
function loadCommandHandlers() {
|
||||||
|
/**
|
||||||
|
* Pull in all the command handlers
|
||||||
|
*/
|
||||||
|
var commands = Object.keys(SUPPORTED_COMMANDS);
|
||||||
|
var paramsSchemas = ['defaultCommandOnly.params']; // Default unless otherwise specified
|
||||||
|
|
||||||
|
for (var i = 0; i < commands.length; ++i) {
|
||||||
|
/**
|
||||||
|
* Default command name is the same as the key
|
||||||
|
*/
|
||||||
|
var command = commands[i];
|
||||||
|
var commandHandler = command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some commands need a custom command handler name.
|
||||||
|
* e.g. `ListDeletedAccounts` uses `ListAccounts`
|
||||||
|
*/
|
||||||
|
var commandInfo = SUPPORTED_COMMANDS[command];
|
||||||
|
if (commandInfo && commandInfo.commandHandler) {
|
||||||
|
commandHandler = commandInfo.commandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `require` the command handler
|
||||||
|
*/
|
||||||
|
commandHandlers[command] = require(
|
||||||
|
path.join(COMMAND_HANDLER_DIR, commandHandler)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some commands have custom parameters validators. If so, add them
|
||||||
|
* to the additional schemas list.
|
||||||
|
*/
|
||||||
|
if (commandInfo && commandInfo.paramsValidator) {
|
||||||
|
paramsSchemas.push(commandInfo.paramsValidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the validator with the same list of supported commands
|
||||||
|
*/
|
||||||
|
var schemas = commands.concat(paramsSchemas);
|
||||||
|
validator.initialise(schemas, config.isDevEnv, SCHEMA_DIR);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Call loadCommandHandlers immediately to register all the handlers we have
|
||||||
|
*/
|
||||||
|
loadCommandHandlers();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Reads the JSON data out of the packet.
|
||||||
|
// req and res are the request and response packets.
|
||||||
|
// remoteAddress: is the source address of the incoming link.
|
||||||
|
// protocolPort is 'HTTPS:443' for example. This is a text string only that is put to the logs.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
exports.handleJSONRequest = function(req, res, remoteAddress, protocolPort, parameters, type) {
|
||||||
|
// Local objects.
|
||||||
|
var serverData = '';
|
||||||
|
var receivedObject = {};
|
||||||
|
|
||||||
|
// Reset number of JSON requests served.
|
||||||
|
exports.JSONServed++;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Receipt of 'data' function.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
var dataReceived = function(chunk) {
|
||||||
|
// Check that the maximum packet size is not above the current limit.
|
||||||
|
if (serverData.length > utils.maxPacketSize) {
|
||||||
|
// Too much data. Shut down receive methods.
|
||||||
|
req.removeListener('data', dataReceived);
|
||||||
|
req.removeListener('end', requestEnd);
|
||||||
|
// Return an error code.
|
||||||
|
res.writeHead(413, {'Content-Type': 'application/json'});
|
||||||
|
res.end(
|
||||||
|
'{\"code\":\"280\",' +
|
||||||
|
'\"info\":\"Packet too large.\"}');
|
||||||
|
log.system(
|
||||||
|
'ATTACK',
|
||||||
|
'Packet too large.',
|
||||||
|
'hJSON.handleJSONRequest',
|
||||||
|
'280',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
} else { // Overflow limit not reached. Add the data.
|
||||||
|
serverData += chunk;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 'End' of data stream.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//jshint -W074
|
||||||
|
var requestEnd = function() {
|
||||||
|
// Protect against unhandled exceptions.
|
||||||
|
try {
|
||||||
|
// Try to parse the data to see if it is JSON.
|
||||||
|
try {
|
||||||
|
// Parse the data received. Some commands are pure rest so skip null data.
|
||||||
|
if (serverData !== '') {
|
||||||
|
if (type === exports.REST) {
|
||||||
|
receivedObject = JSON.parse(serverData);
|
||||||
|
} else if (type === exports.form) {
|
||||||
|
receivedObject = querystring.parse(serverData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// Unable to process querystring or body JSON.
|
||||||
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||||
|
if (type === exports.REST) {
|
||||||
|
res.end(
|
||||||
|
'{\"code\":\"324\",' +
|
||||||
|
'\"info\":\"Invalid JSON in packet.\"}');
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
('Invalid JSON in packet. ' + err.name + ' (' + err.message + ') Message not logged for security reasons.'),
|
||||||
|
'hJSON.handleJSONRequest',
|
||||||
|
'324',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
} else if (type === exports.form) {
|
||||||
|
res.end(
|
||||||
|
'{\"code\":\"325\",' +
|
||||||
|
'\"info\":\"Invalid querystring.\"}');
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
('Invalid querystring. ' + err.name + ' (' + err.message + ') Message not logged for security reasons.'),
|
||||||
|
'hJSON.handleJSONRequest',
|
||||||
|
'325',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
}
|
||||||
|
// Return after error.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detailed logging of input/output for debug purposes. Never show in the live environment.
|
||||||
|
if (exports.showPackets && config.isDevEnv) {
|
||||||
|
if (type === exports.REST) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('[IN] parameters: ' + JSON.stringify(parameters) + ' [REST IN] parsed data: ' +
|
||||||
|
JSON.stringify(receivedObject)),
|
||||||
|
'hJSON.showPackets',
|
||||||
|
'',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
} else if (type === exports.form) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('[IN] parameters: ' + JSON.stringify(parameters) + ' [FORM IN] parsed data: ' +
|
||||||
|
JSON.stringify(receivedObject)),
|
||||||
|
'hJSON.showPackets',
|
||||||
|
'',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create hmac object.
|
||||||
|
*/
|
||||||
|
var hmacData = {};
|
||||||
|
hmacData.address = 'https://' + req.headers.host + req.url;
|
||||||
|
hmacData.method = req.method;
|
||||||
|
hmacData.body = serverData;
|
||||||
|
if ('bridge-timestamp' in req.headers) {
|
||||||
|
hmacData.timestamp = req.headers['bridge-timestamp'];
|
||||||
|
}
|
||||||
|
if ('bridge-hmac' in req.headers) {
|
||||||
|
hmacData.hmac = req.headers['bridge-hmac'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for unknown packets. Note there are a couple of exceptions where commands
|
||||||
|
* can be generated by non-apps.
|
||||||
|
*/
|
||||||
|
if (!(('user-agent' in req.headers) && ((req.headers['user-agent'].substr(0,6)) === 'Bridge'))) {
|
||||||
|
if ((parameters.Command !== 'Register7') &&
|
||||||
|
(parameters.Command !== 'SendReport')) {
|
||||||
|
res.status(403).json({
|
||||||
|
code: '464',
|
||||||
|
info: 'Forbidden.'
|
||||||
|
});
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
'Request forbidden: user-agent is not Bridge.',
|
||||||
|
'hJSON.handleJSONRequest',
|
||||||
|
'464',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build function info.
|
||||||
|
*/
|
||||||
|
var functionInfo = {};
|
||||||
|
functionInfo.name = parameters.Command + '.process';
|
||||||
|
functionInfo.remote = remoteAddress;
|
||||||
|
functionInfo.port = protocolPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this is a command that is handled from the dynamically
|
||||||
|
* loaded and validated commands.
|
||||||
|
*/
|
||||||
|
if (commandHandlers.hasOwnProperty(parameters.Command)) {
|
||||||
|
/**
|
||||||
|
* It is a dynamically created function so handle it here
|
||||||
|
*/
|
||||||
|
doCommand(res, functionInfo, parameters, receivedObject, hmacData);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* All of the hard-coded JSON commands are examined here. The most common should be put at the top.
|
||||||
|
* Once all commands have been converted over to using JSON Schema validation we can
|
||||||
|
* remove this case.
|
||||||
|
*/
|
||||||
|
switch (parameters.Command) {
|
||||||
|
case 'SendReport':
|
||||||
|
SendReport.process(res, functionInfo, parameters);
|
||||||
|
break;
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Error condition - unknown commands.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
default:
|
||||||
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||||
|
res.end(
|
||||||
|
'{\"code\":\"0\",' +
|
||||||
|
'\"info\":\"Unknown Command.\"}');
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
('Unknown \"Command:\" in url (' + parameters.Command + ')'),
|
||||||
|
'hJSON.handleJSONRequest',
|
||||||
|
'0',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// Processing error. Now actual error returned in message
|
||||||
|
var responseObj = {
|
||||||
|
code: '4',
|
||||||
|
info: 'Unhandled Exception - ' + err.message
|
||||||
|
};
|
||||||
|
res.status(200).json(responseObj);
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
('Unhandled Exception - ' + err.name + ' (' + err.message + ')'),
|
||||||
|
'hJSON.handleJSONRequest',
|
||||||
|
'4',
|
||||||
|
'UU',
|
||||||
|
(remoteAddress + ' (' + protocolPort + ')'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Bind events to functions.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
req.on('data', dataReceived); // Data indicates that information is still arriving.
|
||||||
|
req.on('end', requestEnd); // End indicates that everything has been read.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the command specified by `parameters.Command`.
|
||||||
|
* It first validates the body using JSON Schema, then calls the command handler
|
||||||
|
*
|
||||||
|
* @param {!Object} res - Response object for returning information.
|
||||||
|
* @param {!Object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?Object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?Object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {!Object} hmacData - hmac information {!address, !method, !body, ?timestamp, ?hmac}
|
||||||
|
*/
|
||||||
|
function doCommand(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
var commandName = parameters.Command;
|
||||||
|
var commandInfo = SUPPORTED_COMMANDS[commandName];
|
||||||
|
var paramsSchema = commandInfo.paramsValidator || 'defaultCommandOnly.params';
|
||||||
|
|
||||||
|
var bodyValidatorP = validator.validate(commandName, receivedObject);
|
||||||
|
var paramsValidatorP = validator.validate(paramsSchema, parameters);
|
||||||
|
|
||||||
|
Promise.all([bodyValidatorP, paramsValidatorP])
|
||||||
|
.then(function onValidationSucceeded() {
|
||||||
|
/**
|
||||||
|
* Validated, so run the command
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
commandHandlers[commandName].process(
|
||||||
|
res,
|
||||||
|
functionInfo,
|
||||||
|
parameters,
|
||||||
|
receivedObject,
|
||||||
|
hmacData
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
/* Processing error. Return it to the caller */
|
||||||
|
var responseObj = {
|
||||||
|
code: '4',
|
||||||
|
info: 'Unhandled exception - ' + err.message
|
||||||
|
};
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
null, // Don't know what device was used
|
||||||
|
null, // Don't pass in HMAC data as we don't have a device.
|
||||||
|
functionInfo,
|
||||||
|
responseObj,
|
||||||
|
'CRITICAL'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function onValidationFailed(err) {
|
||||||
|
var hasErrorDetail = Array.isArray(err.errors) && err.errors.length > 0;
|
||||||
|
/**
|
||||||
|
* Failed validation, so return an error
|
||||||
|
*/
|
||||||
|
var responseObj = {
|
||||||
|
code: commandInfo.validationErrorCode ?
|
||||||
|
commandInfo.validationErrorCode.toString() :
|
||||||
|
'-1',
|
||||||
|
info: 'Invalid body in request'
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Add a little more detail if we have it */
|
||||||
|
if (hasErrorDetail) {
|
||||||
|
responseObj.info =
|
||||||
|
'Invalid body' +
|
||||||
|
err.errors[0].dataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we are in dev, also return the detailed error info from the validator */
|
||||||
|
if (config.isDevEnv && hasErrorDetail) {
|
||||||
|
responseObj.info += ': ' + err.errors[0].message;
|
||||||
|
responseObj.devOnlyErrorDetail = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
null, // Don't know what device was used
|
||||||
|
null, // Don't pass in HMAC data as we don't have a device.
|
||||||
|
functionInfo,
|
||||||
|
responseObj,
|
||||||
|
'WARNING'
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
72
node_server/ComServe/hJSON/AcceptEULA.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js AcceptEULA Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Confirms that the user has accepted the EULA. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/accepteula/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New version accepted.
|
||||||
|
*/
|
||||||
|
var newLastUpdate = new Date();
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {ClientID: existingClient.ClientID}, {
|
||||||
|
$set: {
|
||||||
|
EULAVersionAccepted: config.EULAVersion,
|
||||||
|
LastUpdate: newLastUpdate
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '290',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updated.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10044',
|
||||||
|
info: 'EULA acceptance confirmed.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('V' + config.EULAVersion + ' EULA acceptance confirmed.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
180
node_server/ComServe/hJSON/AddAddress.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Add Address Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Adds an address which will be associated with the current client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/addaddress/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the requested addresses.
|
||||||
|
* Cyclomatic complexity warning disabled.
|
||||||
|
*/
|
||||||
|
//jshint -W074
|
||||||
|
mainDB.collectionAddresses.find({ClientID: existingClient.ClientID},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
AddressDescription: 1
|
||||||
|
}
|
||||||
|
).toArray(function(err, addresses) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '379',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for maximum number of entries. GTE used to trap problem cases or changes
|
||||||
|
* in config.maxAddresses.
|
||||||
|
*/
|
||||||
|
if (addresses.length >= config.maxAddresses) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '380',
|
||||||
|
info: 'Maximum number of addresses reached.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the description does not already exist.
|
||||||
|
*/
|
||||||
|
var counter;
|
||||||
|
for (counter = 0; counter < addresses.length; counter++) {
|
||||||
|
if (addresses[counter].AddressDescription === receivedObject.AddressDescription) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '381',
|
||||||
|
info: 'AddressDescription already exists.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and populate the new address.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var newAddress = mainDB.blankAddress();
|
||||||
|
newAddress.ClientID = existingClient.ClientID;
|
||||||
|
newAddress.AddressDescription = receivedObject.AddressDescription;
|
||||||
|
if (receivedObject.BuildingNameFlat) {
|
||||||
|
newAddress.BuildingNameFlat = receivedObject.BuildingNameFlat;
|
||||||
|
}
|
||||||
|
newAddress.Address1 = receivedObject.Address1;
|
||||||
|
if (receivedObject.Address2) {
|
||||||
|
newAddress.Address2 = receivedObject.Address2;
|
||||||
|
}
|
||||||
|
newAddress.Town = receivedObject.Town;
|
||||||
|
if (receivedObject.County) {
|
||||||
|
newAddress.County = receivedObject.County;
|
||||||
|
}
|
||||||
|
newAddress.PostCode = receivedObject.PostCode;
|
||||||
|
newAddress.Country = receivedObject.Country;
|
||||||
|
if (receivedObject.PhoneNumber) {
|
||||||
|
newAddress.PhoneNumber = receivedObject.PhoneNumber;
|
||||||
|
}
|
||||||
|
newAddress.DateAdded = timestamp;
|
||||||
|
newAddress.LastUpdate = timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the object to the addresses database.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionAddresses, newAddress, undefined, false, function(err, objectAdded) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '382',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address successfully added.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10053',
|
||||||
|
info: 'Address added.',
|
||||||
|
AddressID: objectAdded[0]._id
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check KYC flag and update if necessary.
|
||||||
|
* note that this is a valid bitwise comparison and manipulation.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (!(existingClient.ClientStatus & utils.ClientAddressMask)) {
|
||||||
|
var newClientStatus = existingClient.ClientStatus | utils.ClientAddressMask;
|
||||||
|
//jshint +W016
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {ClientID: existingDevice.ClientID},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
ClientStatus: newClientStatus,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'ERROR',
|
||||||
|
'Could not set address mask flag set (KYC2) as database was offline.',
|
||||||
|
'AddAddress.process',
|
||||||
|
'',
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System note that the KYC flag has been set.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Client address mask flag set (KYC2).',
|
||||||
|
'AddAddress.process',
|
||||||
|
'',
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//jshint +W074
|
||||||
|
});
|
||||||
|
};
|
336
node_server/ComServe/hJSON/AddCard.js
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js AddCard handling code for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Adds a card to the account table associated to the supplied user.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/addcard/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes needed for this module.
|
||||||
|
*/
|
||||||
|
const _ = require('lodash');
|
||||||
|
var moment = require('moment');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var worldpay = require(global.pathPrefix + 'worldpay.js');
|
||||||
|
var sms = require(global.pathPrefix + 'sms.js');
|
||||||
|
var anon = require(global.pathPrefix + '../utils/anon.js');
|
||||||
|
const acquirers = require(global.pathPrefix + '../utils/acquirers/acquirer.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the client details are set.
|
||||||
|
*/
|
||||||
|
if (!(utils.bitsAllSet(existingClient.ClientStatus, utils.ClientDetailsMask))) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '521',
|
||||||
|
info: 'No client details set.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the address from the database.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAddresses,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectID(receivedObject.BillingAddress),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, existingAddress) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '522',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any addresses match the search query.
|
||||||
|
*/
|
||||||
|
if (!existingAddress) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '523',
|
||||||
|
info: 'Invalid billing address.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Account and fill in as a card.
|
||||||
|
*/
|
||||||
|
var newCard = mainDB.blankAccount();
|
||||||
|
newCard.ClientID = existingClient.ClientID;
|
||||||
|
newCard.ClientAccountName = receivedObject.ClientAccountName;
|
||||||
|
newCard.UserImage = receivedObject.UserImage;
|
||||||
|
newCard.NameOnAccount = receivedObject.NameOnAccount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt and store the card details.
|
||||||
|
*/
|
||||||
|
var temp;
|
||||||
|
/**
|
||||||
|
* CardPAN
|
||||||
|
*/
|
||||||
|
temp = utils.encryptDataV3(receivedObject.CardPAN, receivedObject.ClientKey, existingClient._id.toString());
|
||||||
|
if (typeof temp !== 'string') {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '524',
|
||||||
|
info: 'Error when encrypting CardPAN.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Encryption V3 error: CardPAN, Code: ' + temp.code.toString() + ' (' + temp.message + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newCard.CardPAN = anon.anonymiseCardPAN(receivedObject.CardPAN);
|
||||||
|
newCard.CardPANEncrypted = temp;
|
||||||
|
/**
|
||||||
|
* CardExpiry
|
||||||
|
*/
|
||||||
|
temp = utils.encryptDataV3(receivedObject.CardExpiry, receivedObject.ClientKey, existingClient._id.toString());
|
||||||
|
if (typeof temp !== 'string') {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '524',
|
||||||
|
info: 'Error when encrypting CardExpiry.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Encryption V3 error: CardExpiry, Code: ' + temp.code.toString() + ' (' + temp.message + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newCard.CardExpiryEncrypted = temp;
|
||||||
|
/**
|
||||||
|
* CardValidFrom
|
||||||
|
*/
|
||||||
|
if (receivedObject.CardValidFrom) {
|
||||||
|
temp = utils.encryptDataV3(receivedObject.CardValidFrom, receivedObject.ClientKey, existingClient._id.toString());
|
||||||
|
if (typeof temp !== 'string') {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '524',
|
||||||
|
info: 'Error when encrypting CardValidFrom.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Encryption V3 error: CardValidFrom, Code: ' + temp.code.toString() + ' (' + temp.message + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newCard.CardValidFromEncrypted = temp;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* IssueNumber
|
||||||
|
*/
|
||||||
|
if (receivedObject.IssueNumber) {
|
||||||
|
temp = utils.encryptDataV3(receivedObject.IssueNumber, receivedObject.ClientKey, existingClient._id.toString());
|
||||||
|
if (typeof temp !== 'string') {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '524',
|
||||||
|
info: 'Error when encrypting IssueNumber.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Encryption V3 error: IssueNumber, Code: ' + temp.code.toString() + ' (' + temp.message + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newCard.IssueNumberEncrypted = temp;
|
||||||
|
}
|
||||||
|
newCard.AccountType = 'Credit/Debit Payment Card';
|
||||||
|
newCard.VendorAccountName = 'Credit/Debit Card';
|
||||||
|
newCard.BillingAddress = receivedObject.BillingAddress;
|
||||||
|
var cardDetails = utils.identifyCard(receivedObject.CardPAN);
|
||||||
|
newCard.VendorID = cardDetails.type;
|
||||||
|
newCard.IconLocation = cardDetails.icon;
|
||||||
|
newCard.ReceivingAccount = 0;
|
||||||
|
newCard.PaymentsAccount = 1;
|
||||||
|
newCard.BalanceAvailable = 0;
|
||||||
|
newCard.Balance = null;
|
||||||
|
newCard.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenise the card to check it is valid. The default method can be set in config.
|
||||||
|
* If the default method is 'None' then the details will simply be stored; the assumption is that no checking need
|
||||||
|
* be done at this stage.
|
||||||
|
* Entry of a demo card will show the verification provider as 'Demo'.
|
||||||
|
*/
|
||||||
|
var provider = config.verificationProvider;
|
||||||
|
if (receivedObject.CardPAN === config.demoCardPAN) {
|
||||||
|
provider = 'Demo';
|
||||||
|
}
|
||||||
|
newCard.AcquirerName = provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Worldpay to tokenise the card.
|
||||||
|
*/
|
||||||
|
if (provider === 'Worldpay') {
|
||||||
|
/**
|
||||||
|
* Build the card details for the acquirer to tokenise
|
||||||
|
* This is just an extract from the receivedObject
|
||||||
|
*/
|
||||||
|
const tokeniseDetails = _.pick(
|
||||||
|
receivedObject,
|
||||||
|
[
|
||||||
|
'NameOnAccount',
|
||||||
|
'CardPAN',
|
||||||
|
'CVV',
|
||||||
|
'CardExpiry',
|
||||||
|
'CardValidFrom',
|
||||||
|
'IssueNumber'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make the request to tokenise
|
||||||
|
*/
|
||||||
|
acquirers.tokeniseCard(
|
||||||
|
provider,
|
||||||
|
tokeniseDetails,
|
||||||
|
receivedObject.ClientKey,
|
||||||
|
existingClient._id.toString()
|
||||||
|
).then((cardDetails) => {
|
||||||
|
/**
|
||||||
|
* Update the account object with the new details
|
||||||
|
*/
|
||||||
|
const updatedCard = _.assign(
|
||||||
|
{},
|
||||||
|
newCard,
|
||||||
|
cardDetails
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the card to the Account collection.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionAccount, updatedCard, undefined, false, function(err, objectAdded) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '529',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to client.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10012',
|
||||||
|
info: 'Card added (tokenised with Worldpay).',
|
||||||
|
AccountID: objectAdded[0]._id
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('New card account ID ' + objectAdded[0]._id + ' added (Worldpay ID: ' + ').'));
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
/**
|
||||||
|
* Report an appropriate error to the acquirer not tokenising properly
|
||||||
|
*/
|
||||||
|
if (err.name === acquirers.ERRORS.ACQUIRER_DOWN) {
|
||||||
|
worldpay.commsFailure('AddCard.process');
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '527',
|
||||||
|
info: 'Cannot connect to verifying bank (Worldpay); card addition failed.'
|
||||||
|
},
|
||||||
|
'ERROR');
|
||||||
|
return;
|
||||||
|
} else if (err.name === acquirers.ERRORS.TOKEN_ENCRYPTION_FAILED) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '524',
|
||||||
|
info: 'Error when encrypting Token.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Encryption V3 error when encrypting token. ' + err.info));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '528',
|
||||||
|
info: 'Card tokenisation failed.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Cannot tokenise card. ' + err.name + ':' + err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Do not tokenise the card - simply store the details.
|
||||||
|
* This can either be where the
|
||||||
|
*/
|
||||||
|
else if ((provider === 'None') || (provider === 'Demo')) {
|
||||||
|
/**
|
||||||
|
* Data is simply stored; either a demo card or verification disabled.
|
||||||
|
*/
|
||||||
|
if (provider === 'Demo') {
|
||||||
|
newCard.ClientAccountName += ' (Demo)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the card to the Account collection.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionAccount, newCard, undefined, false, function(err, objectAdded) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '526',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to client.
|
||||||
|
*/
|
||||||
|
var variedInfo = '';
|
||||||
|
if (provider === 'Demo') {
|
||||||
|
variedInfo = '(demo account)';
|
||||||
|
} else {
|
||||||
|
variedInfo = '(not tokenised)';
|
||||||
|
}
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10012',
|
||||||
|
info: 'Card added ' + variedInfo + '.',
|
||||||
|
AccountID: objectAdded[0]._id
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('New card account ID ' + objectAdded[0]._id + ' added ' + variedInfo + '.'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Default case - system disabled.
|
||||||
|
*/
|
||||||
|
else {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '525',
|
||||||
|
info: 'Card addition disabled.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
341
node_server/ComServe/hJSON/AddImage.js
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js AddImage Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Adds an image to the image database.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/image_commands/addimage/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Legacy code line length disable.
|
||||||
|
// jscs:disable maximumLineLength
|
||||||
|
//jshint -W101
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var moment = require('moment');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var credorax = require(global.pathPrefix + 'credorax.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var gm = require('gm');
|
||||||
|
var fs = require('fs');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a buffer and write the image to a temporary disk file.
|
||||||
|
*/
|
||||||
|
var buf = new Buffer(receivedObject.ImageFile, 'base64');
|
||||||
|
var tempFileName = moment().format('YYYYMMDDTHHmmssSSS') + utils.randomCode(utils.fullAlphaNumeric, 14);
|
||||||
|
fs.writeFile((config.temporaryDirectory + tempFileName), buf, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '284',
|
||||||
|
info: 'Internal server error.'
|
||||||
|
},
|
||||||
|
'CRITICAL',
|
||||||
|
('File creation error: ' + err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check this is a valid image file.
|
||||||
|
*/
|
||||||
|
gm((config.temporaryDirectory + tempFileName)).format(function(err, fileFormat) {
|
||||||
|
if ((err) || ((fileFormat !== 'PNG') && (fileFormat !== 'JPEG')) ||
|
||||||
|
((fileFormat === 'PNG') && (receivedObject.FileType !== 'PNG')) ||
|
||||||
|
((fileFormat === 'JPEG') && (receivedObject.FileType !== 'JPG'))) {
|
||||||
|
/**
|
||||||
|
* Add more detail.
|
||||||
|
*/
|
||||||
|
var furtherInfo = '';
|
||||||
|
if (err) {
|
||||||
|
furtherInfo = err;
|
||||||
|
} else if ((fileFormat !== 'PNG') && (fileFormat !== 'JPEG')) {
|
||||||
|
furtherInfo = 'Not a PNG or JPEG file';
|
||||||
|
} else {
|
||||||
|
furtherInfo = 'File format is not that specified in FileType';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let the user know the image file is invalid.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '285',
|
||||||
|
info: 'Invalid image file.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Invalid image file (' + furtherInfo + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the file size is reasonable.
|
||||||
|
*/
|
||||||
|
gm((config.temporaryDirectory + tempFileName)).size(function(err, fileSize) {
|
||||||
|
if ((err) || (fileSize.width > utils.ImageWidthMax) ||
|
||||||
|
(fileSize.height > utils.ImageHeightMax) ||
|
||||||
|
(fileSize.width !== fileSize.height)) {
|
||||||
|
/**
|
||||||
|
* Add more detail.
|
||||||
|
*/
|
||||||
|
var furtherInfo = '';
|
||||||
|
if (err) {
|
||||||
|
furtherInfo = err;
|
||||||
|
} else if (fileSize.width !== fileSize.height) {
|
||||||
|
furtherInfo = 'The image is not square';
|
||||||
|
} else if (fileSize.width > utils.ImageWidthMax) {
|
||||||
|
furtherInfo = 'File is over ' + utils.ImageWidthMax + ' pixels wide';
|
||||||
|
} else {
|
||||||
|
furtherInfo = 'File is over ' + utils.ImageHeightMax + ' pixels high';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let the user know the image file is invalid.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '286',
|
||||||
|
info: 'Image dimensions invalid.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Image dimensions invalid (' + furtherInfo + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image is valid. Push image to Bluemix.
|
||||||
|
*/
|
||||||
|
config.bluemixContainer.createObject(tempFileName, receivedObject.ImageFile)
|
||||||
|
.then(function() {
|
||||||
|
/**
|
||||||
|
* Create new database entry.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var newImage = {};
|
||||||
|
newImage.ClientID = existingClient.ClientID;
|
||||||
|
newImage.ImageFile = tempFileName;
|
||||||
|
newImage.ImageType = receivedObject.ImageType;
|
||||||
|
newImage.FileType = receivedObject.FileType;
|
||||||
|
newImage.ImageReported = 0;
|
||||||
|
newImage.LastUpdate = timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the image and get the object ID back.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionImages, newImage, undefined, false, function(err, imageAdded) {
|
||||||
|
if (err) { // Unable to store info.
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '218',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image successfully added. Update the client record.
|
||||||
|
*/
|
||||||
|
var ImageID = mongodb.ObjectID(imageAdded[0]._id).toString();
|
||||||
|
var Selfie = existingClient.Selfie;
|
||||||
|
if (receivedObject.ImageType === 'Selfie') {
|
||||||
|
Selfie = ImageID;
|
||||||
|
}
|
||||||
|
var CompanyLogo = existingClient.Merchant[0].CompanyLogo;
|
||||||
|
if (receivedObject.ImageType === 'CompanyLogo0') {
|
||||||
|
CompanyLogo = ImageID;
|
||||||
|
}
|
||||||
|
var newLastVersion = existingClient.LastVersion + 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the client record.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionClient,
|
||||||
|
{ClientID: existingClient.ClientID}, {
|
||||||
|
$set: {
|
||||||
|
Selfie: Selfie,
|
||||||
|
'Merchant.0.CompanyLogo': CompanyLogo,
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
LastVersion: newLastVersion
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '219',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the temporary file.
|
||||||
|
*/
|
||||||
|
fs.unlink((config.temporaryDirectory + tempFileName), function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '287',
|
||||||
|
info: 'Internal server error.'
|
||||||
|
},
|
||||||
|
'CRITICAL',
|
||||||
|
('File deletion error: ' + err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return code to user.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10031',
|
||||||
|
info: 'Image added.',
|
||||||
|
ImageRef: ImageID
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('New ' + receivedObject.FileType + ' image added (' + fileSize.width +
|
||||||
|
'x' + fileSize.height + ', ID ' + ImageID + ', S3 ' + tempFileName + ').'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch async cleanup task to remove old image if it was never used.
|
||||||
|
* This can be slow so is launched independently of image addition.
|
||||||
|
*/
|
||||||
|
var searchFor = '';
|
||||||
|
if (receivedObject.ImageType === 'Selfie') {
|
||||||
|
searchFor = existingClient.Selfie;
|
||||||
|
} else {
|
||||||
|
searchFor = existingClient.Merchant[0].CompanyLogo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protect the default images.
|
||||||
|
*/
|
||||||
|
if ((searchFor === config.defaultSelfie) || (searchFor === config.defaultCompanyLogo0)) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Default image detected (not deleted).',
|
||||||
|
'AddImage.process',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainDB.findOneObject(mainDB.collectionTransactionHistory, {OtherImage: searchFor},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, oldTransaction) {
|
||||||
|
if (!err) {
|
||||||
|
/**
|
||||||
|
* Database module writes its own errors.
|
||||||
|
*/
|
||||||
|
if (oldTransaction) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('Old ' + receivedObject.FileType + ' image in use (ID ' + searchFor +
|
||||||
|
'). Not deleted.'),
|
||||||
|
'AddImage.process',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image not used in a transaction.
|
||||||
|
* Pull the database reference.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionImages, {_id: mongodb.ObjectID(searchFor)},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, oldImage) {
|
||||||
|
if (!err) {
|
||||||
|
// Database module writes its own errors.
|
||||||
|
if (!oldImage) {
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
('Cannot find Image in database (ID ' + searchFor + ').'),
|
||||||
|
'AddImage.process',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image is present. Remove from IBM OS.
|
||||||
|
*/
|
||||||
|
config.bluemixContainer.deleteObject(oldImage.ImageFile)
|
||||||
|
.then(function() {
|
||||||
|
/**
|
||||||
|
* Remove the Images database entry.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionImages, {_id: mongodb.ObjectID(searchFor)}, undefined, false, function(err) {
|
||||||
|
if (!err) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('Unused ' + receivedObject.FileType + ' deleted from IBM OS (ID ' + searchFor +
|
||||||
|
', OS ' + oldImage.ImageFile + ').'),
|
||||||
|
'AddImage.process',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
(receivedObject.FileType + ' image not deleted from IBM OS (ID ' + searchFor + ', OS ' +
|
||||||
|
oldImage.ImageFile + '). ' + err.message),
|
||||||
|
'AddImage.process',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
/**
|
||||||
|
* Error putting the file on S3.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '463',
|
||||||
|
info: 'Could not store image.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Error uploading image to Bluemix Object Storage (' + err.message + ').'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
130
node_server/ComServe/hJSON/Authorise2FARequest.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js handler to authorise an outstanding 2FA request for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Authorises the identified 2FA request from a web console login request
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/authorise2farequest/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var Q = require('q');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This authorises an outstanding 2-factor authentication request arising from
|
||||||
|
* an attempt to log into the web console. The RequestID for this request
|
||||||
|
* comes from a previous request to Get2FARequest.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorise the identified request, if it can be found.
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - TargetAccount must match the id of the current client
|
||||||
|
* - RequestID must match the RequestID
|
||||||
|
* - Request must not be expired
|
||||||
|
* - Request must not be already authorised (i.e. no authorised date)
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var query = {
|
||||||
|
TargetAccount: existingClient.ClientID,
|
||||||
|
RequestID: receivedObject.RequestID,
|
||||||
|
RequestExpiry: {$gt: timestamp},
|
||||||
|
AuthorisedDate: {$type: 10} // Must exist and be exactly `null` (BSON Type 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the update
|
||||||
|
*/
|
||||||
|
var newExpiry = new Date(timestamp);
|
||||||
|
newExpiry.setSeconds(
|
||||||
|
timestamp.getSeconds() + utils.twoFactorRequestExpiry
|
||||||
|
);
|
||||||
|
var update = {
|
||||||
|
$set: {
|
||||||
|
AuthorisedDate: timestamp,
|
||||||
|
AuthorisingDeviceID: existingDevice.DeviceUuid,
|
||||||
|
RequestExpiry: newExpiry,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the options.
|
||||||
|
*/
|
||||||
|
var options = {
|
||||||
|
upsert: false,
|
||||||
|
multi: false,
|
||||||
|
comment: 'authorise2FARequest' // For profiler logs use
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the object
|
||||||
|
*/
|
||||||
|
Q.nfcall(
|
||||||
|
mainDB.updateObject,
|
||||||
|
mainDB.collectionTwoFARequests,
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
options,
|
||||||
|
false
|
||||||
|
).then(function(result) {
|
||||||
|
/**
|
||||||
|
* Successful query (though it may not have found anything to update)
|
||||||
|
*/
|
||||||
|
if (result.result.n === 1) {
|
||||||
|
/**
|
||||||
|
* A document was updated, so this is total success
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10067',
|
||||||
|
info: 'Authorisation successful.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Authorise2FARequest successful: ' + receivedObject.RequestID));
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* The request ran, but didn't find any documents
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '457',
|
||||||
|
info: 'Invalid or expired request ID.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Invalid or expired request ID: ' + receivedObject.RequestID));
|
||||||
|
}
|
||||||
|
}).catch(function() {
|
||||||
|
/**
|
||||||
|
* Query failed, most likely from the database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '456',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
198
node_server/ComServe/hJSON/CancelPaymentRequest.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js CancelPaymentRequest Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Cancels a payment request, sets TransactionStatus appropriately.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/payment_commands/cancelpaymentrequest/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either the payer or payee can cancel the transaction.
|
||||||
|
*/
|
||||||
|
//jshint -W074
|
||||||
|
mainDB.findOneObject(mainDB.collectionTransaction, {_id: mongodb.ObjectID(receivedObject.TransactionID)},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, existingTransaction) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '162',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the transaction exists.
|
||||||
|
*/
|
||||||
|
if (existingTransaction === null) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '163',
|
||||||
|
info: 'Cannot find transaction.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that it's either the customer or merchant requesting cancellation.
|
||||||
|
* If this is true it indicated a session timeout.
|
||||||
|
*/
|
||||||
|
if (!(((receivedObject.DeviceToken === existingTransaction.CustomerDeviceToken) &&
|
||||||
|
(receivedObject.SessionToken === existingTransaction.CustomerSessionToken)) ||
|
||||||
|
((receivedObject.DeviceToken === existingTransaction.MerchantDeviceToken) &&
|
||||||
|
(receivedObject.SessionToken === existingTransaction.MerchantSessionToken)))) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '164',
|
||||||
|
info: 'Session timed out.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User is allowed to cancel the transaction. Default is to update the transaction.
|
||||||
|
*/
|
||||||
|
var updateTransaction = false;
|
||||||
|
var newTransactionStatus;
|
||||||
|
var newStatusInfo;
|
||||||
|
switch (existingTransaction.TransactionStatus) {
|
||||||
|
case 0:
|
||||||
|
/**
|
||||||
|
* Just issued.
|
||||||
|
*/
|
||||||
|
newTransactionStatus = 10;
|
||||||
|
newStatusInfo = 'Paycode cancelled before use by customer.';
|
||||||
|
updateTransaction = true;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/**
|
||||||
|
* Code claimed.
|
||||||
|
*/
|
||||||
|
newTransactionStatus = 11;
|
||||||
|
if (receivedObject.DeviceToken === existingTransaction.CustomerDeviceToken) {
|
||||||
|
newStatusInfo = 'Payment process cancelled by customer.';
|
||||||
|
} else {
|
||||||
|
newStatusInfo = 'Payment process cancelled by merchant.';
|
||||||
|
}
|
||||||
|
updateTransaction = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
/**
|
||||||
|
* Payment underway.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '165',
|
||||||
|
info: 'Transfer underway.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
/**
|
||||||
|
* Payment complete.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '166',
|
||||||
|
info: 'Payment complete.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '167',
|
||||||
|
info: 'Payment already cancelled.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the transaction if needed.
|
||||||
|
*/
|
||||||
|
if (updateTransaction) {
|
||||||
|
var newLastUpdate = new Date();
|
||||||
|
var newLastVersion = existingTransaction.LastVersion + 1;
|
||||||
|
mainDB.updateObject(mainDB.collectionTransaction,
|
||||||
|
{_id: mongodb.ObjectID(receivedObject.TransactionID)}, {
|
||||||
|
$set: {
|
||||||
|
TransactionStatus: newTransactionStatus,
|
||||||
|
StatusInfo: newStatusInfo,
|
||||||
|
LastUpdate: newLastUpdate,
|
||||||
|
LastVersion: newLastVersion
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '168',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete PayCode so it cannot be matched again.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionPayCode,
|
||||||
|
{_id: existingTransaction.PayCodeID}, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '169',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment successfully cancelled.
|
||||||
|
*/
|
||||||
|
let cancelledBy = '';
|
||||||
|
if (receivedObject.DeviceToken === existingTransaction.CustomerDeviceToken) {
|
||||||
|
cancelledBy = 'payer.';
|
||||||
|
} else {
|
||||||
|
cancelledBy = 'payee.';
|
||||||
|
}
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10018',
|
||||||
|
info: ('Transaction cancelled by ' + cancelledBy)
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('PayCode ' + existingTransaction.PayCode + ' cancelled by ' + cancelledBy));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
101
node_server/ComServe/hJSON/ChangePIN.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js ChangePIN Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets a list of all devices associated with a particular client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/changepin/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the current PIN.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
auth.checkDevicePIN(receivedObject.DeviceAuthorisation, existingDevice, timestamp, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt and store the new PIN. Create a new salt as well.
|
||||||
|
*/
|
||||||
|
auth.encryptPBKDF2(receivedObject.NewAuthorisation, function(err, newSalt, newHash) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '416',
|
||||||
|
info: ('Error encrypting new PIN: ' + err)
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success. Update the database with the new PIN.
|
||||||
|
*/
|
||||||
|
var newDeviceAuthorisation = config.pinCryptoVersion + '::' + newHash;
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: existingDevice.DeviceToken},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
DeviceSalt: newSalt,
|
||||||
|
DeviceAuthorisation: newDeviceAuthorisation,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '417',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10057',
|
||||||
|
info: 'PIN changed.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
121
node_server/ComServe/hJSON/ChangePassword.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js ChangePassword Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Allows the user to change their password if they know the old one.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/changepassword/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var templates = require(global.pathPrefix + '../utils/templates.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the current Password.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
auth.checkClientPassword(receivedObject.Password, existingClient, timestamp, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt and store the new Password. Create a new salt as well.
|
||||||
|
*/
|
||||||
|
auth.encryptPBKDF2(receivedObject.NewPassword, function(err, newSalt, newHash) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '419',
|
||||||
|
info: ('Error encrypting new Password: ' + err)
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the user that a new device is being added - send an e-mail.
|
||||||
|
*/
|
||||||
|
var htmlEmail = templates.render('password-changed', {
|
||||||
|
DeviceNumber: existingDevice.DeviceNumber
|
||||||
|
});
|
||||||
|
mailer.sendEmail(null, existingClient.ClientName, 'Bridge Password Changed',
|
||||||
|
htmlEmail, 'ChangePassword.process', function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '421',
|
||||||
|
info: 'Unable to send e-mail.'
|
||||||
|
},
|
||||||
|
'ERROR');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success. Update the database with the new Password.
|
||||||
|
*/
|
||||||
|
var newPassword = config.passwordCryptoVersion + '::' + newHash;
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {ClientID: existingClient.ClientID},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
ClientSalt: newSalt,
|
||||||
|
Password: newPassword,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '420',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10058',
|
||||||
|
info: 'Password changed.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
243
node_server/ComServe/hJSON/ConfirmInvoice.js
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Confirm Invoice Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Confirms the transaction for the customer.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/merchant_commands/confirm_invoice/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
/**
|
||||||
|
* Includes.
|
||||||
|
*/
|
||||||
|
const debug = require('debug')('app:ConfirmInvoice');
|
||||||
|
const auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
const utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
const impl = require(global.pathPrefix + '../impl/confirm_transaction.js');
|
||||||
|
const acqErrors = require(global.pathPrefix + '../utils/acquirers/acquirer_errors.js');
|
||||||
|
const responsesUtils = require(global.pathPrefix + '../utils/responses.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The customer confirms the invoice, and pays it using their selected account.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect the data we need
|
||||||
|
*/
|
||||||
|
const data = {
|
||||||
|
TransactionID: receivedObject.InvoiceID,
|
||||||
|
TipAmount: 0,
|
||||||
|
ClientKey: receivedObject.ClientKey,
|
||||||
|
initialStatus: utils.TransactionStatus.PENDING_INVOICE,
|
||||||
|
ipAddress: functionInfo.remote,
|
||||||
|
Latitude: receivedObject.Latitude,
|
||||||
|
Longitude: receivedObject.Longitude,
|
||||||
|
AccountID: receivedObject.AccountID
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the base implementation
|
||||||
|
*/
|
||||||
|
impl.confirmTransaction(existingClient, existingDevice, data).then(() => {
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
existingDevice,
|
||||||
|
hmacData,
|
||||||
|
functionInfo,
|
||||||
|
{
|
||||||
|
code: '10074',
|
||||||
|
info: 'Invoice confirmed.',
|
||||||
|
TransactionID: receivedObject.InvoiceID
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Invoice ID <' + receivedObject.InvoiceID + '> confirmed by acquirer.'
|
||||||
|
);
|
||||||
|
}).catch((error) => {
|
||||||
|
debug('Error:', error);
|
||||||
|
//
|
||||||
|
// Define the responses
|
||||||
|
//
|
||||||
|
const responses = [
|
||||||
|
//
|
||||||
|
// Errors when reading from the database
|
||||||
|
//
|
||||||
|
[
|
||||||
|
'MongoError',
|
||||||
|
200, 510, 'Database Offline', true
|
||||||
|
],
|
||||||
|
|
||||||
|
//
|
||||||
|
// Errors from the main implementation
|
||||||
|
//
|
||||||
|
[
|
||||||
|
impl.ERRORS.TRANSACTION_NOT_FOUND,
|
||||||
|
200, 496, 'Invalid InvoiceID'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_NOT_FOUND,
|
||||||
|
200, 551, 'Merchant information not found'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CLIENT_DETAILS_NOT_SET,
|
||||||
|
200, 552, 'User details not set'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_DETAILS_NOT_SET,
|
||||||
|
200, 553, 'Merchant details not set'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CLIENT_KYC_INCOMPLETE,
|
||||||
|
200, 554, 'Additional user information required'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_KYC_INCOMPLETE,
|
||||||
|
200, 555, 'Additional merchant information required'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.TRANSACTION_TOTAL_TOO_HIGH,
|
||||||
|
200, 310, ('Total above current limit of ' + utils.transactionMaxText)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.TRANSACTION_TOTAL_TOO_LOW,
|
||||||
|
200, 311, ('Total below current limit of ' + utils.transactionMinText)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_SET_CONFIRMED,
|
||||||
|
200, 510, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_SET_COMPLETE,
|
||||||
|
200, 506, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_ADD_HISTORY,
|
||||||
|
200, 507, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_UPDATE_CUSTOMER_BALANCE,
|
||||||
|
200, 508, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_UPDATE_MERCHANT_BALANCE,
|
||||||
|
200, 509, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_ACCOUNT_NOT_FOUND,
|
||||||
|
200, 497, 'Invalid Merchant AccountID'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CUSTOMER_ACCOUNT_NOT_FOUND,
|
||||||
|
200, 494, 'Invalid Customer AccountID'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_ACCOUNT_NOT_RECEIVING,
|
||||||
|
200, 498, 'Not a receiving account'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CUSTOMER_ACCOUNT_NOT_PAYMENTS,
|
||||||
|
200, 495, 'Not a payments account'
|
||||||
|
],
|
||||||
|
|
||||||
|
//
|
||||||
|
// Errors from the acquirer
|
||||||
|
//
|
||||||
|
[
|
||||||
|
acqErrors.UNKNOWN_ACQUIRER,
|
||||||
|
200, 532, 'Merchant acquirer unknown',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_COMBINATION,
|
||||||
|
200, 536, 'Invalid payment type',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_DOWN,
|
||||||
|
200, 533, 'Cannot connect to acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_MERCHANT_NAME,
|
||||||
|
200, 534, 'Invalid Merchant account details.',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_MERCHANT_ACCOUNT_DETAILS,
|
||||||
|
200, 535, 'Receiving account information unreadable',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_CARD_DETAILS,
|
||||||
|
200, 536, 'Payment account information unreadable',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_UNKNOWN_ERROR,
|
||||||
|
200, 537, 'Error processing payment',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_BAD_REQUEST,
|
||||||
|
200, 538, 'Error processing payment',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_INVALID_PAYMENT_DETAILS,
|
||||||
|
200, 540, 'Invalid payment details',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_UNAUTHORIZED,
|
||||||
|
200, 541, 'Merchant account unauthorized with acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_MERCHANT_DISABLED,
|
||||||
|
200, 542, 'Merchant account disabled with acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_INTERNAL_SERVER_ERROR,
|
||||||
|
200, 543, 'Error processing payment',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.CARD_EXPIRED,
|
||||||
|
200, 544, 'Card has expired',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.PAYMENT_FAILED_UNSPECIFIED,
|
||||||
|
200, 545, 'Unspecified error',
|
||||||
|
true
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const responseHandler = new responsesUtils.ErrorResponses(responses);
|
||||||
|
responseHandler.respondAuth(
|
||||||
|
res, error, existingDevice, hmacData, functionInfo, 'INFO'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
241
node_server/ComServe/hJSON/ConfirmTransaction.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js ConfirmTransaction Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Confirms the transaction for the customer.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/payment_commands/confirmtransaction/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Legacy code line length disable.
|
||||||
|
// jscs:disable maximumLineLength
|
||||||
|
//jshint -W101
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes.
|
||||||
|
*/
|
||||||
|
const log = require(global.pathPrefix + 'log.js');
|
||||||
|
const auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
const utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
const impl = require(global.pathPrefix + '../impl/confirm_transaction.js');
|
||||||
|
const acqErrors = require(global.pathPrefix + '../utils/acquirers/acquirer_errors.js');
|
||||||
|
const responsesUtils = require(global.pathPrefix + '../utils/responses.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
TransactionID: receivedObject.TransactionID,
|
||||||
|
TipAmount: receivedObject.TipAmount,
|
||||||
|
ClientKey: receivedObject.ClientKey,
|
||||||
|
initialStatus: utils.TransactionStatus.CLAIMED,
|
||||||
|
ipAddress: functionInfo.remote
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the base implementation
|
||||||
|
*/
|
||||||
|
impl.confirmTransaction(existingClient, existingDevice, data).then(() => {
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
existingDevice,
|
||||||
|
hmacData,
|
||||||
|
functionInfo,
|
||||||
|
{
|
||||||
|
code: '10023',
|
||||||
|
info: 'Transaction confirmed by acquirer.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Transaction ID <' + receivedObject.TransactionID + '> confirmed by acquirer.'
|
||||||
|
);
|
||||||
|
}).catch((error) => {
|
||||||
|
//
|
||||||
|
// Define the responses
|
||||||
|
//
|
||||||
|
const responses = [
|
||||||
|
//
|
||||||
|
// Errors when reading from the database
|
||||||
|
//
|
||||||
|
[
|
||||||
|
'MongoError',
|
||||||
|
200, 182, 'Database Offline', true
|
||||||
|
],
|
||||||
|
|
||||||
|
//
|
||||||
|
// Errors from the main implementation
|
||||||
|
//
|
||||||
|
[
|
||||||
|
impl.ERRORS.TRANSACTION_NOT_FOUND,
|
||||||
|
200, 183, 'Invalid TransactionID'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_NOT_FOUND,
|
||||||
|
200, 546, 'Merchant information not found'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CLIENT_DETAILS_NOT_SET,
|
||||||
|
200, 547, 'User details not set'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_DETAILS_NOT_SET,
|
||||||
|
200, 548, 'Merchant\'s user details not set'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CLIENT_KYC_INCOMPLETE,
|
||||||
|
200, 549, 'Additional user information required'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_KYC_INCOMPLETE,
|
||||||
|
200, 550, 'Additional user information for merchant required'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.TRANSACTION_TOTAL_TOO_HIGH,
|
||||||
|
200, 310, ('Total above current limit of ' + utils.transactionMaxText)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.TRANSACTION_TOTAL_TOO_LOW,
|
||||||
|
200, 311, ('Total below current limit of ' + utils.transactionMinText)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_SET_CONFIRMED,
|
||||||
|
200, 185, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_SET_COMPLETE,
|
||||||
|
200, 185, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_ADD_HISTORY,
|
||||||
|
200, 188, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_UPDATE_CUSTOMER_BALANCE,
|
||||||
|
200, 239, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.FAILED_UPDATE_MERCHANT_BALANCE,
|
||||||
|
200, 240, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_ACCOUNT_NOT_FOUND,
|
||||||
|
200, 209, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CUSTOMER_ACCOUNT_NOT_FOUND,
|
||||||
|
200, 208, 'Database offline'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.MERCHANT_ACCOUNT_NOT_RECEIVING,
|
||||||
|
200, 211, 'Invalid merchant AccountID'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
impl.ERRORS.CUSTOMER_ACCOUNT_NOT_PAYMENTS,
|
||||||
|
200, 210, 'Invalid customer AccountID'
|
||||||
|
],
|
||||||
|
|
||||||
|
//
|
||||||
|
// Errors from the acquirer
|
||||||
|
//
|
||||||
|
[
|
||||||
|
acqErrors.UNKNOWN_ACQUIRER,
|
||||||
|
200, 532, 'Merchant acquirer unknown',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_COMBINATION,
|
||||||
|
200, 539, 'Invalid payment type',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_DOWN,
|
||||||
|
200, 533, 'Cannot connect to acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_MERCHANT_NAME,
|
||||||
|
200, 534, 'Invalid merchant details',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_MERCHANT_ACCOUNT_DETAILS,
|
||||||
|
200, 535, 'Receiving account information unreadable',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.INVALID_CARD_DETAILS,
|
||||||
|
200, 536, 'Payment account information unreadable',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_UNKNOWN_ERROR,
|
||||||
|
200, 537, 'Error processing payment',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_BAD_REQUEST,
|
||||||
|
200, 538, 'Error processing payment',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_INVALID_PAYMENT_DETAILS,
|
||||||
|
200, 540, 'Invalid payment type for merchant acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_UNAUTHORIZED,
|
||||||
|
200, 541, 'Merchant account unauthorized with acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_MERCHANT_DISABLED,
|
||||||
|
200, 542, 'Merchant account disabled with acquirer',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.ACQUIRER_INTERNAL_SERVER_ERROR,
|
||||||
|
200, 543, 'Error processing payment',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
acqErrors.CARD_EXPIRED,
|
||||||
|
200, 544, 'Card Expired',
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acqErrors.PAYMENT_FAILED_UNSPECIFIED,
|
||||||
|
200, 545, 'Error processing payment',
|
||||||
|
true
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const responseHandler = new responsesUtils.ErrorResponses(responses);
|
||||||
|
responseHandler.respondAuth(
|
||||||
|
res, error, existingDevice, hmacData, functionInfo, 'INFO'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
97
node_server/ComServe/hJSON/DeleteAccount.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js DeleteAccount Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2017 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Deletes the account ID sent through.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/deleteaccount/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
const auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
const acquirerUtils = require(global.pathPrefix + '../utils/acquirers/acquirer.js');
|
||||||
|
const responsesUtils = require(global.pathPrefix + '../utils/responses.js');
|
||||||
|
const deleteAccountImpl = require(global.pathPrefix + '../impl/delete_account.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - Detail on the calling function {!name, !remote, !port}.
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Call the implementation and return the properly formatted
|
||||||
|
//
|
||||||
|
return deleteAccountImpl.deleteAccount(existingClient.ClientID, receivedObject.AccountID)
|
||||||
|
.then(() => {
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
return auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: '10016',
|
||||||
|
info: 'Account successfully deleted.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Account deleted (ID ' + receivedObject.AccountID + ').')
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const responses = [
|
||||||
|
[
|
||||||
|
deleteAccountImpl.ERRORS.RELATED_INVOICES,
|
||||||
|
200, 30108, 'Account can\'t be deleted while related active invoices exist', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
// AccountID is not valid (or doesn't belong to *me*)
|
||||||
|
deleteAccountImpl.ERRORS.NOT_FOUND,
|
||||||
|
200, 153, 'No account match.', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
// AccountID is not valid (or doesn't belong to *me*)
|
||||||
|
deleteAccountImpl.ERRORS.FAILED_UPDATE,
|
||||||
|
200, 153, 'No account match.', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
deleteAccountImpl.ERRORS.LOCKED,
|
||||||
|
200, 243, 'Account locked.', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acquirerUtils.ERRORS.UNKNOWN_ACQUIRER,
|
||||||
|
200, 241, 'Invalid VendorID or AcquirerName.', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
acquirerUtils.ERRORS.ACQUIRER_DOWN,
|
||||||
|
200, 244, 'Cannot connect to acquiring bank', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MongoError',
|
||||||
|
200, 152, 'Database offline.'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const responseHandler = new responsesUtils.ErrorResponses(responses);
|
||||||
|
|
||||||
|
return responseHandler.respondAuth(
|
||||||
|
res, error, existingDevice, hmacData, functionInfo, 'INFO'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
});
|
||||||
|
};
|
158
node_server/ComServe/hJSON/DeleteAddress.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Delete Address Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Deletes an address which is associated with the current client.
|
||||||
|
* This call will fail if the address is used with any accounts.
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/deleteaddress/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if this address is in use.
|
||||||
|
*/
|
||||||
|
mainDB.collectionAccount.find(
|
||||||
|
{
|
||||||
|
ClientID: existingClient.ClientID,
|
||||||
|
BillingAddress: receivedObject.AddressID,
|
||||||
|
AccountStatus: {$bitsAllClear: utils.AccountDeleted}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
ClientID: 1
|
||||||
|
}
|
||||||
|
).toArray(function(err, accounts) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '385',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any addresses match the search query.
|
||||||
|
*/
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '386',
|
||||||
|
info: 'Address still in use.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Address still in use in ' + accounts.length + ' account(s).'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full address detail to back up.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAddresses,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectID(receivedObject.AddressID),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, existingAddress) {
|
||||||
|
/**
|
||||||
|
* Check for errors.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '387',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any addresses match the search query.
|
||||||
|
*/
|
||||||
|
if (!existingAddress) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '388',
|
||||||
|
info: 'Cannot find address to delete.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up address.
|
||||||
|
*/
|
||||||
|
existingAddress.AddressID = existingAddress._id.toString();
|
||||||
|
delete existingAddress._id;
|
||||||
|
existingAddress.LastUpdate = new Date();
|
||||||
|
existingAddress.LastVersion += 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store to AddressArchive database.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionAddressArchive, existingAddress, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '389',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete from Address database.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionAddresses, {_id: mongodb.ObjectID(receivedObject.AddressID)},
|
||||||
|
undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '390',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address successfully deleted.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10054',
|
||||||
|
info: 'Address deleted.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
162
node_server/ComServe/hJSON/DeleteDevice.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Delete Device Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Deletes a particular device if it belongs to the current client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/deletedevice/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Local variables
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the user has permission to delete the device.
|
||||||
|
*/
|
||||||
|
auth.checkClientPassword(receivedObject.Password, existingClient, timestamp, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the device.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectId(receivedObject.DeviceIndex),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, device) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '369',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No hits from database.
|
||||||
|
*/
|
||||||
|
if (!device) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '370',
|
||||||
|
info: 'Invalid device or device does not belong to client.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the selected device has not been locked by Comcarde.
|
||||||
|
*/
|
||||||
|
if (utils.bitsAllSet(device.DeviceStatus, utils.DeviceBarredMask)) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '371',
|
||||||
|
info: 'The device has been put on hold by Comcarde.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cannot delete the device that is being used.
|
||||||
|
*/
|
||||||
|
if (device.DeviceNumber === existingDevice.DeviceNumber) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '374',
|
||||||
|
info: 'Cannot delete the device currently in use.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the old object _id as DeviceIndex
|
||||||
|
*/
|
||||||
|
device.DeviceIndex = device._id.toString();
|
||||||
|
delete device._id;
|
||||||
|
device.DeviceAuthorisation = '';
|
||||||
|
device.DeviceSalt = '';
|
||||||
|
device.CurrentHMAC = '';
|
||||||
|
device.PendingHMAC = '';
|
||||||
|
device.LastUpdate = timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the object to the Archive.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionDeviceArchive, device, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '372',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device can be safely deleted.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionDevice,
|
||||||
|
{_id: mongodb.ObjectId(receivedObject.DeviceIndex)}, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '373',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10051',
|
||||||
|
info: 'Device deleted.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
132
node_server/ComServe/hJSON/DeleteMessage.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Delete Message Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Deletes a particular message if it belongs to the current client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/deletemessage/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a Message by moving it to the MessageArchive collection
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the message, confirming it belongs to this client
|
||||||
|
*/
|
||||||
|
var findQuery = {
|
||||||
|
_id: mongodb.ObjectId(receivedObject.MessageID),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
};
|
||||||
|
|
||||||
|
mainDB.findOneObject(mainDB.collectionMessages,
|
||||||
|
findQuery,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, message) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '486',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No hits from database.
|
||||||
|
*/
|
||||||
|
if (!message) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '489',
|
||||||
|
info: 'Invalid message or message does not belong to client.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone the message for copying to the archive.
|
||||||
|
* Also copy _id to MessageID, remove _id, and updated the
|
||||||
|
* LastUpdate time.
|
||||||
|
*/
|
||||||
|
var messageClone = _.clone(message);
|
||||||
|
messageClone.MessageID = message._id.toHexString();
|
||||||
|
delete messageClone._id;
|
||||||
|
|
||||||
|
messageClone.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the object to the Archive.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(
|
||||||
|
mainDB.collectionMessagesArchive,
|
||||||
|
messageClone,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '487',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message can be safely deleted.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(
|
||||||
|
mainDB.collectionMessages,
|
||||||
|
findQuery,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '488',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: '10073',
|
||||||
|
info: 'Message deleted.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
102
node_server/ComServe/hJSON/ElevateSession.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Elevate a session with the email and password.
|
||||||
|
*
|
||||||
|
* Requires that you are already logged in on a device.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/elevatesession/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
const _ = require('lodash');
|
||||||
|
const Q = require('q');
|
||||||
|
|
||||||
|
const authP = require(global.pathPrefix + 'auth-promises.js');
|
||||||
|
const utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the email and password is correct. It also requires that you are logged
|
||||||
|
* in normally, so we also verify that the given details match those for the
|
||||||
|
* normal device login.
|
||||||
|
*
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = async function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
let authDetails = null;
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Check the device session is valid
|
||||||
|
*/
|
||||||
|
authDetails = await authP.validSession(
|
||||||
|
res,
|
||||||
|
receivedObject.DeviceToken,
|
||||||
|
receivedObject.SessionToken,
|
||||||
|
functionInfo,
|
||||||
|
hmacData
|
||||||
|
).then((result) => {
|
||||||
|
return {
|
||||||
|
existingDevice: result[0],
|
||||||
|
existingClient: result[1]
|
||||||
|
};
|
||||||
|
}).catch(() => Q.reject()); // Error has been handled in auth.ValidSession
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the email address sent to us is the same one as the one signed in to the device
|
||||||
|
*/
|
||||||
|
if (authDetails.existingClient.ClientName !== receivedObject.ClientName) {
|
||||||
|
throw utils.createError(559, 'Invalid ClientName.', 'ElevateSesession');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the password is correct for the current user
|
||||||
|
*/
|
||||||
|
const timestamp = new Date();
|
||||||
|
await authP.checkClientPassword(
|
||||||
|
receivedObject.Password,
|
||||||
|
authDetails.existingClient,
|
||||||
|
timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All good so return success
|
||||||
|
*/
|
||||||
|
authP.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
authDetails.existingDevice,
|
||||||
|
hmacData,
|
||||||
|
functionInfo,
|
||||||
|
{
|
||||||
|
code: '10079',
|
||||||
|
info: 'Session Elevated.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'ElevateSession successful'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
/**
|
||||||
|
* If we have an error then return.
|
||||||
|
* NOTE: we won't have an error if it was validSession() that failed as
|
||||||
|
* it handles responding internally.
|
||||||
|
*/
|
||||||
|
if (error) {
|
||||||
|
authP.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
_.get(authDetails, 'existingDevice', null),
|
||||||
|
hmacData,
|
||||||
|
functionInfo,
|
||||||
|
{
|
||||||
|
code: String(_.get(error, 'code', -1)),
|
||||||
|
info: _.get(error, 'message', 'Unknown error')
|
||||||
|
},
|
||||||
|
'ERROR',
|
||||||
|
'ElevateSession failed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
112
node_server/ComServe/hJSON/Get2FARequest.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Get outstanding 2-factor requests handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets the client's outstanding 2 factor requests for access to the web console
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/get2farequest/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var Q = require('q');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets any outstanding 2-factor requests that are created when a client tries
|
||||||
|
* to log in to the web console. These should be responded to by the apps with
|
||||||
|
* a call to Authorise2FARequest.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the latest outstanding 2FA request from the table
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - TargetAccount must match the id of the current client
|
||||||
|
* - Request must not be expired
|
||||||
|
* - Request must not be already authorised (i.e. no authorised date)
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var query = {
|
||||||
|
TargetAccount: existingClient.ClientID,
|
||||||
|
RequestExpiry: {$gt: timestamp},
|
||||||
|
AuthorisedDate: {$type: 10} // Must exist and be exactly `null` (BSON Type 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 0,
|
||||||
|
RequestID: 1,
|
||||||
|
RequestDate: 1,
|
||||||
|
RequestExpiry: 1,
|
||||||
|
RequesterDisplayName: 1,
|
||||||
|
RequesterClientID: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Build the options. Importantly, this includes a sort by RequestDate
|
||||||
|
* so that we always get the request that was made most recently (as
|
||||||
|
* there could be multiple requests that are still active).
|
||||||
|
*/
|
||||||
|
var options = {
|
||||||
|
fields: projection,
|
||||||
|
sort: {RequestDate: -1},
|
||||||
|
comment: 'get2FARequest' // For profiler logs use
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the object
|
||||||
|
*/
|
||||||
|
Q.nfcall(
|
||||||
|
mainDB.findOneObject,
|
||||||
|
mainDB.collectionTwoFARequests,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
false
|
||||||
|
).then(function(result) {
|
||||||
|
/**
|
||||||
|
* Successful query (though it may not have found anything)
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10066',
|
||||||
|
info: 'Request returned.',
|
||||||
|
Request: result // Will be null if no requests outstanding
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Get2FARequest successful - ' + (result ? 'request pending' : 'no requests')));
|
||||||
|
}).catch(function() {
|
||||||
|
/**
|
||||||
|
* Query failed, most likely from the database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '454',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
67
node_server/ComServe/hJSON/GetClientDetails.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Get Client Details Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets the client's personal details from the client record.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/getclientdetails/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the client details are set.
|
||||||
|
*/
|
||||||
|
if (!(utils.bitsAllSet(existingClient.ClientStatus, utils.ClientDetailsMask))) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '425',
|
||||||
|
info: 'No details set.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10060',
|
||||||
|
info: 'Client details retrieved.',
|
||||||
|
Title: existingClient.KYC[0].Title,
|
||||||
|
FirstName: existingClient.KYC[0].FirstName,
|
||||||
|
LastName: existingClient.KYC[0].LastName,
|
||||||
|
MiddleNames: existingClient.KYC[0].MiddleNames,
|
||||||
|
ResidentialAddressID: existingClient.KYC[0].ResidentialAddressID || '',
|
||||||
|
Gender: existingClient.KYC[0].Gender || ''
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
};
|
141
node_server/ComServe/hJSON/GetImage.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js GetImage Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Retrieves an image from the image database.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/image_commands/getimage/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for default selfie.
|
||||||
|
*/
|
||||||
|
if (receivedObject.ImageRef === config.defaultSelfie) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10032',
|
||||||
|
info: 'Image found.',
|
||||||
|
FileType: 'PNG',
|
||||||
|
ImageFile: config.defaultSelfieData,
|
||||||
|
ImageReported: 0
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Default image sent (defaultSelfie).'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for default Company Logo.
|
||||||
|
*/
|
||||||
|
if (receivedObject.ImageRef === config.defaultCompanyLogo0) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10032',
|
||||||
|
info: 'Image found.',
|
||||||
|
FileType: 'PNG',
|
||||||
|
ImageFile: config.defaultCompanyLogo0Data,
|
||||||
|
ImageReported: 0
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Default image sent (defaultCompanyLogo0).'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The image needs to be retrieved form the object store. First, get the details from MongoDB.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionImages,
|
||||||
|
{_id: mongodb.ObjectID(receivedObject.ImageRef)}, undefined, false, function(err, existingImage) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '221',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the image exists.
|
||||||
|
*/
|
||||||
|
if (!existingImage) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '222',
|
||||||
|
info: 'Invalid ImageRef.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Client requested an invalid ImageRef.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if it has been reported.
|
||||||
|
*/
|
||||||
|
var newImageReported = 0;
|
||||||
|
if (existingImage.ImageReported !== 0) {
|
||||||
|
newImageReported = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up Bluemix transaction.
|
||||||
|
*/
|
||||||
|
config.bluemixContainer.getObject(existingImage.ImageFile)
|
||||||
|
.then(function(object) {
|
||||||
|
object.load(false)
|
||||||
|
.then(function(content) {
|
||||||
|
/**
|
||||||
|
* All good. Return it to the user.
|
||||||
|
*/
|
||||||
|
var fileString = content.toString();
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10032',
|
||||||
|
info: 'Image found.',
|
||||||
|
FileType: existingImage.FileType,
|
||||||
|
ImageFile: fileString,
|
||||||
|
ImageReported: newImageReported
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Image sent (ID ' + receivedObject.ImageRef + ', IBM OS ' + existingImage.ImageFile + ', IR' +
|
||||||
|
existingImage.ImageReported + ').'));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '289',
|
||||||
|
info: 'Cannot access image.'
|
||||||
|
},
|
||||||
|
'ERROR',
|
||||||
|
('Cannot get image from IBM OS (' + err.message + ').'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
148
node_server/ComServe/hJSON/GetInvoice.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Returns full details for the selected invoice
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/merchant_commands/get_invoice/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var apiHelpers = require(global.pathPrefix + '../utils/api_helpers.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the full details for the selected invoice. This only returns invoices
|
||||||
|
* where the client is the *customer*.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in invoice body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the invoice from the database
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - ClientID of the invoice must match the current client
|
||||||
|
* - _id must match the given InvoiceID
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
_id: mongodb.ObjectID(receivedObject.InvoiceID),
|
||||||
|
CustomerClientID: existingClient.ClientID
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 1,
|
||||||
|
MerchantDisplayName: 1,
|
||||||
|
MerchantSubDisplayName: 1,
|
||||||
|
MerchantImage: 1,
|
||||||
|
TransactionStatus: 1,
|
||||||
|
MerchantInvoice: 1,
|
||||||
|
MerchantComment: 1,
|
||||||
|
MerchantVATNo: 1,
|
||||||
|
CustomerComment: 1,
|
||||||
|
RequestAmount: 1,
|
||||||
|
DueDate: 1,
|
||||||
|
LastUpdate: 1,
|
||||||
|
MerchantInvoiceNumber: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
fields: projection,
|
||||||
|
comment: 'GetInvoice' // For profiler logs use
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the invoice
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(
|
||||||
|
mainDB.collectionTransaction,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
false, // Don't suppress errors
|
||||||
|
function(err, existingInvoice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '514',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for no invoices.
|
||||||
|
*/
|
||||||
|
if (!existingInvoice) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '515',
|
||||||
|
info: 'Invalid InvoiceID.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the creation date and rename the fields
|
||||||
|
*/
|
||||||
|
existingInvoice.CreationDate = existingInvoice._id.getTimestamp();
|
||||||
|
var renames = {
|
||||||
|
_id: 'InvoiceID',
|
||||||
|
MerchantDisplayName: 'OtherDisplayName',
|
||||||
|
MerchantSubDisplayName: 'OtherSubDisplayName',
|
||||||
|
MerchantImage: 'OtherImage'
|
||||||
|
};
|
||||||
|
apiHelpers.renameFields(existingInvoice, renames);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy just the InvoiceNumber up to the top level
|
||||||
|
//
|
||||||
|
if (!_.isUndefined(existingInvoice.MerchantInvoiceNumber)) {
|
||||||
|
existingInvoice.MerchantInvoiceNumber =
|
||||||
|
existingInvoice.MerchantInvoiceNumber.InvoiceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set a value for any missing dates
|
||||||
|
if (_.isUndefined(existingInvoice.DueDate)) {
|
||||||
|
existingInvoice.DueDate = '1970-01-01T00:00:00.000Z';
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
existingDevice,
|
||||||
|
hmacData,
|
||||||
|
functionInfo, {
|
||||||
|
code: '10076',
|
||||||
|
info: 'Invoice returned.',
|
||||||
|
InvoiceDetail: existingInvoice
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Invoice detail returned (InvoiceID ' + receivedObject.InvoiceID + ').'
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
121
node_server/ComServe/hJSON/GetMessage.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Comcarde GetMessage Handler.
|
||||||
|
//
|
||||||
|
// Command = GetMessage
|
||||||
|
// Gets more information on a particular message associated with Client.
|
||||||
|
// Checked 25/5/2015 KJS
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Includes
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the messages from the database
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - ClientID of the message must match the current client
|
||||||
|
* - _id must match the given MessageID
|
||||||
|
* - TimeFilter must be before <now>
|
||||||
|
* - Type must match the request type, if any
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
_id: mongodb.ObjectID(receivedObject.MessageID),
|
||||||
|
ClientID: existingClient.ClientID,
|
||||||
|
TimeFilter: {$lte: new Date()}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 1,
|
||||||
|
Type: 1,
|
||||||
|
Read: 1,
|
||||||
|
Reference: 1,
|
||||||
|
Info: 1,
|
||||||
|
InfoObject: 1,
|
||||||
|
LastUpdate: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
fields: projection,
|
||||||
|
comment: 'GetMessage' // For profiler logs use
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the message
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(
|
||||||
|
mainDB.collectionMessages,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
false,
|
||||||
|
function(err, existingMessage) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '480',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for no messages.
|
||||||
|
*/
|
||||||
|
if (!existingMessage) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '481',
|
||||||
|
info: 'Invalid MessageID.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move _id to MessageID, add the creation date
|
||||||
|
*/
|
||||||
|
existingMessage.MessageID = existingMessage._id;
|
||||||
|
existingMessage.CreationDate = existingMessage._id.getTimestamp();
|
||||||
|
delete existingMessage._id;
|
||||||
|
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
existingDevice,
|
||||||
|
hmacData,
|
||||||
|
functionInfo, {
|
||||||
|
code: '10071',
|
||||||
|
info: 'Message returned.',
|
||||||
|
Message: existingMessage
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Message detail returned (MessageID ' + receivedObject.MessageID + ').'
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
145
node_server/ComServe/hJSON/GetTransactionDetail.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Comcarde GetTransactionDetail Handler.
|
||||||
|
//
|
||||||
|
// Command = GetTransactionDetail
|
||||||
|
// Gets more information on a particular transaction associated with Client.
|
||||||
|
// Checked 25/5/2015 KJS
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Includes
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is no timestamp object, create one.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionTransaction,
|
||||||
|
{_id: mongodb.ObjectID(receivedObject.TransactionID)}, undefined, false, function(err, existingTransaction) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '191',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for no transactions.
|
||||||
|
*/
|
||||||
|
if (!existingTransaction) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '192',
|
||||||
|
info: 'Invalid TransactionID.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer.
|
||||||
|
*/
|
||||||
|
var toReturn = {};
|
||||||
|
if (existingTransaction.CustomerClientID === existingClient.ClientID) {
|
||||||
|
toReturn.OtherDisplayName = existingTransaction.MerchantDisplayName;
|
||||||
|
toReturn.OtherSubDisplayName = existingTransaction.MerchantSubDisplayName;
|
||||||
|
toReturn.OtherImage = existingTransaction.MerchantImage;
|
||||||
|
toReturn.TransactionStatus = existingTransaction.TransactionStatus;
|
||||||
|
toReturn.StatusInfo = existingTransaction.StatusInfo;
|
||||||
|
toReturn.MerchantInvoice = existingTransaction.MerchantInvoice;
|
||||||
|
toReturn.MerchantComment = existingTransaction.MerchantComment;
|
||||||
|
toReturn.MerchantVATNo = existingTransaction.MerchantVATNo;
|
||||||
|
toReturn.RequestAmount = existingTransaction.RequestAmount;
|
||||||
|
toReturn.TipAmount = existingTransaction.TipAmount;
|
||||||
|
toReturn.TotalAmount = existingTransaction.TotalAmount;
|
||||||
|
toReturn.MyLocation = existingTransaction.CustomerLocation;
|
||||||
|
toReturn.SaleTime = existingTransaction.SaleTime;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy just the InvoiceNumber up to the top level
|
||||||
|
//
|
||||||
|
if (!_.isUndefined(existingTransaction.MerchantInvoiceNumber)) {
|
||||||
|
toReturn.MerchantInvoiceNumber =
|
||||||
|
existingTransaction.MerchantInvoiceNumber.InvoiceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10026',
|
||||||
|
info: 'Detail returned.',
|
||||||
|
TransactionDetail: toReturn
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Transaction detail returned (TransactionID ' + mongodb.ObjectID(existingTransaction._id).toString() + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merchant.
|
||||||
|
*/
|
||||||
|
if (existingTransaction.MerchantClientID === existingClient.ClientID) {
|
||||||
|
toReturn.OtherDisplayName = existingTransaction.CustomerDisplayName;
|
||||||
|
toReturn.OtherSubDisplayName = existingTransaction.CustomerSubDisplayName;
|
||||||
|
toReturn.OtherImage = existingTransaction.CustomerImage;
|
||||||
|
toReturn.TransactionStatus = existingTransaction.TransactionStatus;
|
||||||
|
toReturn.StatusInfo = existingTransaction.StatusInfo;
|
||||||
|
toReturn.MerchantInvoice = existingTransaction.MerchantInvoice;
|
||||||
|
toReturn.MerchantComment = existingTransaction.MerchantComment;
|
||||||
|
toReturn.MerchantVATNo = existingTransaction.MerchantVATNo;
|
||||||
|
toReturn.RequestAmount = existingTransaction.RequestAmount;
|
||||||
|
toReturn.TipAmount = existingTransaction.TipAmount;
|
||||||
|
toReturn.TotalAmount = existingTransaction.TotalAmount;
|
||||||
|
toReturn.MyLocation = existingTransaction.MerchantLocation;
|
||||||
|
toReturn.SaleTime = existingTransaction.SaleTime;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy just the InvoiceNumber up to the top level
|
||||||
|
//
|
||||||
|
if (!_.isUndefined(existingTransaction.MerchantInvoiceNumber)) {
|
||||||
|
toReturn.MerchantInvoiceNumber =
|
||||||
|
existingTransaction.MerchantInvoiceNumber.InvoiceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10026',
|
||||||
|
info: 'Detail returned.',
|
||||||
|
TransactionDetail: toReturn
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Transaction detail returned (TransactionID ' + mongodb.ObjectID(existingTransaction._id).toString() + ').'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Didn't find the client name.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '193',
|
||||||
|
info: 'Invalid ClientID.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
115
node_server/ComServe/hJSON/GetTransactionHistory.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js GetTransactionHistory Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets a high level list of all the transactions associated with a Client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/gettransactionhistory/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is no timestamp object, create one.
|
||||||
|
*/
|
||||||
|
var timestamp;
|
||||||
|
if (receivedObject.Timestamp) {
|
||||||
|
timestamp = new Date(receivedObject.Timestamp);
|
||||||
|
} else {
|
||||||
|
timestamp = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the account filter if needed.
|
||||||
|
*/
|
||||||
|
var accountFilter = '';
|
||||||
|
if (receivedObject.AccountID) {
|
||||||
|
accountFilter = receivedObject.AccountID;
|
||||||
|
} else {
|
||||||
|
accountFilter = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the requested items.
|
||||||
|
*/
|
||||||
|
mainDB.collectionTransactionHistory.find(
|
||||||
|
{
|
||||||
|
ClientID: existingClient.ClientID,
|
||||||
|
AccountID: {$regex: accountFilter},
|
||||||
|
SaleTime: {$lt: timestamp}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 0,
|
||||||
|
TransactionID: 1,
|
||||||
|
TransactionType: 1,
|
||||||
|
AccountID: 1,
|
||||||
|
OtherDisplayName: 1,
|
||||||
|
OtherSubDisplayName: 1,
|
||||||
|
OtherImage: 1,
|
||||||
|
MyLocation: 1,
|
||||||
|
TotalAmount: 1,
|
||||||
|
SaleTime: 1,
|
||||||
|
MerchantInvoiceNumber: 1
|
||||||
|
}
|
||||||
|
).skip(receivedObject.Skip).limit(receivedObject.Number).sort({'SaleTime': -1}).toArray(function(err, items) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '187',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy just the InvoiceNumber up to the top level
|
||||||
|
//
|
||||||
|
for (var i = 0; i < items.length; ++i) {
|
||||||
|
if (!_.isUndefined(items[i].MerchantInvoiceNumber)) {
|
||||||
|
items[i].MerchantInvoiceNumber =
|
||||||
|
items[i].MerchantInvoiceNumber.InvoiceNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10025',
|
||||||
|
info: 'History returned.',
|
||||||
|
count: items.length,
|
||||||
|
TransactionHistory: items
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Transaction history requested from ' + (receivedObject.Skip + 1) + ' to ' +
|
||||||
|
(receivedObject.Skip + receivedObject.Number) + ' (' + items.length + ' items found).'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
50
node_server/ComServe/hJSON/GetTransactionUpdate.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js GetTransactionUpdate Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Allows a user to check a transaction to see what's happening.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/payment_commands/gettransactionupdate/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var impl = require(global.pathPrefix + '../impl/get_transaction_update.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl.getTransactionUpdate(receivedObject, function(err, result) {
|
||||||
|
//
|
||||||
|
// The error or result will be the correct body to send back
|
||||||
|
// to the caller
|
||||||
|
//
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, err);
|
||||||
|
} else {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
61
node_server/ComServe/hJSON/IconCache.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js IconCache Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Returns current icon versions.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/image_commands/iconcache/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
|
||||||
|
var cardIconCache = [
|
||||||
|
{ImageName: 'AMEX.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'bridge-card.png', VersionNo: '2'},
|
||||||
|
{ImageName: 'BRIDGE_MERCHANT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'CARTEBLEUE.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'credorax-account.png', VersionNo: '3'},
|
||||||
|
{ImageName: 'Dankort.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'DINERS.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'Diners-Generic.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'Discover-card.png', VersionNo: '2'},
|
||||||
|
{ImageName: 'Electron.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'Generic-card.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'JCB.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'LloydsTSB.png', VersionNo: '2'},
|
||||||
|
{ImageName: 'MAESTRO.png', VersionNo: '2'},
|
||||||
|
{ImageName: 'MASTERCARD_CORPORATE_CREDIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'MASTERCARD_CORPORATE_DEBIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'MASTERCARD_CREDIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'MASTERCARD_DEBIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'MIR.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'RBS.png', VersionNo: '2'},
|
||||||
|
{ImageName: 'VISA_CORPORATE_CREDIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'VISA_CORPORATE_DEBIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'VISA_CREDIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'VISA_DEBIT.png', VersionNo: '1'},
|
||||||
|
{ImageName: 'worldpay-account.png', VersionNo: '2'}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10017',
|
||||||
|
info: 'IconCache check successful.',
|
||||||
|
cache: cardIconCache
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Icon cache sent.');
|
||||||
|
};
|
78
node_server/ComServe/hJSON/ImageCache.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Image Cache Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Retrieves an image from the image database.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/image_commands/imagecache/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the image cache.
|
||||||
|
*/
|
||||||
|
var imageCache = [];
|
||||||
|
imageCache.push({
|
||||||
|
ImageType: 'defaultSelfie',
|
||||||
|
ImageRef: config.defaultSelfie
|
||||||
|
});
|
||||||
|
imageCache.push({
|
||||||
|
ImageType: 'Selfie',
|
||||||
|
ImageRef: existingClient.Selfie
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Merchant logos if applicable.
|
||||||
|
*/
|
||||||
|
if (existingClient.Merchant[0].MerchantStatus === 1) {
|
||||||
|
imageCache.push({
|
||||||
|
ImageType: 'defaultCompanyLogo0',
|
||||||
|
ImageRef: config.defaultCompanyLogo0
|
||||||
|
});
|
||||||
|
imageCache.push({
|
||||||
|
ImageType: 'CompanyLogo0',
|
||||||
|
ImageRef: existingClient.Merchant[0].CompanyLogo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current images from the cache.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10033',
|
||||||
|
info: 'ImageCache check successful.',
|
||||||
|
Images: imageCache
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Image cache sent.');
|
||||||
|
});
|
||||||
|
};
|
47
node_server/ComServe/hJSON/KeepAlive.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js KeepAlive Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Simple refresh of the session token.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/keepalive/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the KeepAlive was successful.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10028',
|
||||||
|
info: 'Keep alive successful.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
};
|
281
node_server/ComServe/hJSON/ListAccounts.js
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js List Accounts Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Returns a list of accounts for the referenced user.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/listaccounts/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var anon = require(global.pathPrefix + '../utils/anon.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the EULA version.
|
||||||
|
*/
|
||||||
|
if (existingClient.EULAVersionAccepted !== config.EULAVersion) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '293',
|
||||||
|
info: 'EULA must be accepted before continuing.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session login successful. Get an account list from the database (includes all data,
|
||||||
|
* except accounts that were create from the API)
|
||||||
|
* Cyclomatic complexity warning due to legacy code.
|
||||||
|
*/
|
||||||
|
//jshint -W074, -W071
|
||||||
|
const query = {
|
||||||
|
ClientID: existingClient.ClientID,
|
||||||
|
AccountStatus: {
|
||||||
|
$bitsAllClear: utils.AccountApiCreated
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mainDB.collectionAccount.find(query).toArray(function(err, items) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '121',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client has no accounts in the system.
|
||||||
|
*/
|
||||||
|
if (items === null) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '122',
|
||||||
|
info: 'No Items.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'No accounts.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the account information.
|
||||||
|
*/
|
||||||
|
var counter = 0;
|
||||||
|
var newArray = [];
|
||||||
|
var newAccount = {};
|
||||||
|
var issues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deal with listing of deleted accounts only.
|
||||||
|
*/
|
||||||
|
var listDeleted = 0;
|
||||||
|
if (parameters.Command === 'ListDeletedAccounts') {
|
||||||
|
listDeleted = utils.AccountDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go through each item and return a subset of information.
|
||||||
|
*/
|
||||||
|
while (counter < items.length) {
|
||||||
|
/**
|
||||||
|
* Check for Merchant status: only merchants can see receiving accounts.
|
||||||
|
* Don't show if it is deleted (unless we are asked to show deleted only).
|
||||||
|
* The disable for JS Hint is because there is a bitwise comparison.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (((existingClient.Merchant[0].MerchantStatus === 1) ||
|
||||||
|
(items[counter].AccountType !== 'Credit/Debit Receiving Account')) &&
|
||||||
|
((items[counter].AccountStatus & utils.AccountDeleted) === listDeleted)) {
|
||||||
|
//jshint +W016
|
||||||
|
/**
|
||||||
|
* Select card information.
|
||||||
|
*/
|
||||||
|
newAccount = {};
|
||||||
|
newAccount.AccountID = items[counter]._id;
|
||||||
|
newAccount.AccountType = items[counter].AccountType;
|
||||||
|
newAccount.ClientAccountName = items[counter].ClientAccountName;
|
||||||
|
newAccount.BillingAddress = items[counter].BillingAddress;
|
||||||
|
newAccount.VendorID = items[counter].VendorID;
|
||||||
|
newAccount.VendorAccountName = items[counter].VendorAccountName;
|
||||||
|
newAccount.NameOnAccount = items[counter].NameOnAccount;
|
||||||
|
newAccount.ReceivingAccount = items[counter].ReceivingAccount;
|
||||||
|
newAccount.PaymentsAccount = items[counter].PaymentsAccount;
|
||||||
|
newAccount.BalanceAvailable = items[counter].BalanceAvailable;
|
||||||
|
if (newAccount.BalanceAvailable) {
|
||||||
|
newAccount.Balance = items[counter].Balance;
|
||||||
|
}
|
||||||
|
newAccount.IconLocation = items[counter].IconLocation;
|
||||||
|
newAccount.Integrity = items[counter].Integrity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch out for merchant images should the person have previously been a Merchant.
|
||||||
|
*/
|
||||||
|
if (existingClient.Merchant[0].MerchantStatus === 0) {
|
||||||
|
if ((items[counter].UserImage === 'defaultCompanyLogo0') ||
|
||||||
|
(items[counter].UserImage === 'CompanyLogo0')) {
|
||||||
|
newAccount.UserImage = 'defaultSelfie';
|
||||||
|
} else {
|
||||||
|
newAccount.UserImage = items[counter].UserImage;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newAccount.UserImage = items[counter].UserImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull and verify information and account access.
|
||||||
|
* Note that only the *Encrypted fields (e.g. CardPANEncrypted) store the actual details.
|
||||||
|
* No sensitive information is ever stored as plain text.
|
||||||
|
*/
|
||||||
|
issues = [];
|
||||||
|
if (newAccount.AccountType === 'Credit/Debit Payment Card') {
|
||||||
|
/**
|
||||||
|
* Check the card details using the keys.
|
||||||
|
*/
|
||||||
|
var CardPANEncryptedResult = null;
|
||||||
|
var CardValidFromEncryptedResult = null;
|
||||||
|
var CardExpiryEncryptedResult = null;
|
||||||
|
var IssueNumberEncryptedResult = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the card details first.
|
||||||
|
* Note that detailed error reporting is coming back but it is ignored for the moment.
|
||||||
|
* Please check the logs for this information.
|
||||||
|
*/
|
||||||
|
if (items[counter].CardPANEncrypted !== '') {
|
||||||
|
CardPANEncryptedResult = utils.checkAccountInformation(
|
||||||
|
items[counter].CardPANEncrypted,
|
||||||
|
receivedObject.ClientKey,
|
||||||
|
existingClient._id.toString(),
|
||||||
|
items[counter]._id.toString(),
|
||||||
|
'CardPANEncrypted');
|
||||||
|
if (CardPANEncryptedResult) {
|
||||||
|
issues.push(utils.ACCOUNT_ERR.CARD_PAN_DEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items[counter].CardValidFromEncrypted !== '') {
|
||||||
|
CardValidFromEncryptedResult = utils.checkAccountInformation(
|
||||||
|
items[counter].CardValidFromEncrypted,
|
||||||
|
receivedObject.ClientKey,
|
||||||
|
existingClient._id.toString(),
|
||||||
|
items[counter]._id.toString(),
|
||||||
|
'CardValidFromEncrypted');
|
||||||
|
if (CardValidFromEncryptedResult) {
|
||||||
|
issues.push(utils.ACCOUNT_ERR.CARD_VALID_DEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items[counter].CardExpiryEncrypted !== '') {
|
||||||
|
CardExpiryEncryptedResult = utils.checkAccountInformation(
|
||||||
|
items[counter].CardExpiryEncrypted,
|
||||||
|
receivedObject.ClientKey,
|
||||||
|
existingClient._id.toString(),
|
||||||
|
items[counter]._id.toString(),
|
||||||
|
'CardExpiryEncrypted');
|
||||||
|
if (CardExpiryEncryptedResult) {
|
||||||
|
// Nesting too deep; functionality here for now for readability.
|
||||||
|
//jshint -W073
|
||||||
|
if (CardExpiryEncryptedResult.code === 5) {
|
||||||
|
issues.push(CardExpiryEncryptedResult.message);
|
||||||
|
} else {
|
||||||
|
issues.push(utils.ACCOUNT_ERR.CARD_EXP_DEC);
|
||||||
|
}
|
||||||
|
//jshint +W073
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items[counter].IssueNumberEncrypted !== '') {
|
||||||
|
IssueNumberEncryptedResult = utils.checkAccountInformation(
|
||||||
|
items[counter].IssueNumberEncrypted,
|
||||||
|
receivedObject.ClientKey,
|
||||||
|
existingClient._id.toString(),
|
||||||
|
items[counter]._id.toString(),
|
||||||
|
'IssueNumberEncrypted');
|
||||||
|
if (IssueNumberEncryptedResult) {
|
||||||
|
issues.push(utils.ACCOUNT_ERR.CARD_ISS_DEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the anonymised data.
|
||||||
|
*/
|
||||||
|
newAccount.CardPAN = items[counter].CardPAN;
|
||||||
|
} else if (newAccount.AccountType === 'Bank Account') {
|
||||||
|
newAccount.AccountNumber = items[counter].AccountNumber;
|
||||||
|
newAccount.SortCode = items[counter].SortCode;
|
||||||
|
} else if (newAccount.AccountType === 'Credit/Debit Receiving Account') {
|
||||||
|
var AcquirerMerchantIDEncryptedResult = null;
|
||||||
|
if (items[counter].AcquirerMerchantID !== '') {
|
||||||
|
AcquirerMerchantIDEncryptedResult = utils.decryptDataV1(items[counter].AcquirerMerchantID);
|
||||||
|
if (typeof AcquirerMerchantIDEncryptedResult === 'string') {
|
||||||
|
newAccount.AcquirerMerchantID = AcquirerMerchantIDEncryptedResult;
|
||||||
|
} else {
|
||||||
|
issues.push(utils.ACCOUNT_ERR.CARD_ISS_DEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the responses.
|
||||||
|
*/
|
||||||
|
if (issues.length !== 0) {
|
||||||
|
if (newAccount.Integrity !== null) {
|
||||||
|
newAccount.Integrity = newAccount.Integrity.concat(issues);
|
||||||
|
} else {
|
||||||
|
newAccount.Integrity = issues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push onto new array subset.
|
||||||
|
*/
|
||||||
|
newArray.push(newAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always increment the counter.
|
||||||
|
*/
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the account information.
|
||||||
|
*/
|
||||||
|
var defaultAccount = existingDevice.DefaultAccount;
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10013',
|
||||||
|
info: 'Accounts list sent.',
|
||||||
|
DefaultAccount: defaultAccount,
|
||||||
|
AccountList: newArray
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
//jshint +W074, +W071
|
||||||
|
});
|
||||||
|
};
|
95
node_server/ComServe/hJSON/ListAddresses.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js List Addresses Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets a list of all addresses associated with a particular client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/listaddresses/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var anon = require(global.pathPrefix + '../utils/anon.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the requested addresses.
|
||||||
|
*/
|
||||||
|
mainDB.collectionAddresses.find(
|
||||||
|
{
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
AddressDescription: 1,
|
||||||
|
BuildingNameFlat: 1,
|
||||||
|
Address1: 1,
|
||||||
|
Address2: 1,
|
||||||
|
Town: 1,
|
||||||
|
County: 1,
|
||||||
|
PostCode: 1,
|
||||||
|
Country: 1,
|
||||||
|
PhoneNumber: 1,
|
||||||
|
ResidentTo: 1,
|
||||||
|
ResidentFrom: 1
|
||||||
|
}
|
||||||
|
).toArray(function(err, addresses) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '377',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up device number detail.
|
||||||
|
*/
|
||||||
|
var counter;
|
||||||
|
for (counter = 0; counter < addresses.length; counter++) {
|
||||||
|
addresses[counter].AddressID = addresses[counter]._id.toString();
|
||||||
|
delete addresses[counter]._id;
|
||||||
|
anon.anonymiseAddress(addresses[counter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10052',
|
||||||
|
info: 'Address list returned.',
|
||||||
|
AddressCount: addresses.length,
|
||||||
|
MaxAddresses: config.maxAddresses,
|
||||||
|
Addresses: addresses
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Address list requested: ' + addresses.length + ' found (Max. ' + config.maxAddresses + ').'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
93
node_server/ComServe/hJSON/ListDevices.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js List Devices Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets a list of all devices associated with a particular client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/listdevices/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var anon = require(global.pathPrefix + '../utils/anon.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the requested items.
|
||||||
|
*/
|
||||||
|
mainDB.collectionDevice.find(
|
||||||
|
{
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
DeviceName: 1,
|
||||||
|
DeviceNumber: 1,
|
||||||
|
DeviceStatus: 1,
|
||||||
|
DeviceHardware: 1,
|
||||||
|
DeviceSoftware: 1,
|
||||||
|
LastLoginLocation: 1,
|
||||||
|
LastLoginIP: 1,
|
||||||
|
LastLogin: 1,
|
||||||
|
LastUpdate: 1
|
||||||
|
}
|
||||||
|
).toArray(function(err, items) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '362',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up device number detail.
|
||||||
|
*/
|
||||||
|
var counter = 0;
|
||||||
|
while (counter < items.length) {
|
||||||
|
items[counter].DeviceIndex = items[counter]._id;
|
||||||
|
delete items[counter]._id;
|
||||||
|
anon.anonymiseDevice(items[counter]);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10050',
|
||||||
|
info: 'Device list returned.',
|
||||||
|
DeviceCount: items.length,
|
||||||
|
MaxDevices: existingClient.MaxDevices,
|
||||||
|
Devices: items
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Device list requested: ' + items.length + ' device(s) found (Max. ' + existingClient.MaxDevices + ').'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
149
node_server/ComServe/hJSON/ListInvoices.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview List the invoices that are outstanding for the logged in user to pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/merchant_commands/list_invoices/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var apiHelpers = require(global.pathPrefix + '../utils/api_helpers.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all invoices for the current client. This is oly the invoices that the
|
||||||
|
* client is the *customer* for. Invoices where the client is the *merchant*
|
||||||
|
* can be accessd through the console.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the list of invoices from the database
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - ClientID of the invoice must match the current client
|
||||||
|
* - Must have an invoice number
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
CustomerClientID: existingClient.ClientID,
|
||||||
|
MerchantInvoiceNumber: {
|
||||||
|
$exists: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we've been given a last modified date, then we want only invoices
|
||||||
|
* modified since then.
|
||||||
|
*/
|
||||||
|
if (receivedObject.ModifiedSince) {
|
||||||
|
query.LastUpdate = {
|
||||||
|
$gte: new Date(receivedObject.ModifiedSince)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 1,
|
||||||
|
TransactionStatus: 1,
|
||||||
|
MerchantDisplayName: 1,
|
||||||
|
MerchantSubDisplayName: 1,
|
||||||
|
MerchantImage: 1,
|
||||||
|
RequestAmount: 1,
|
||||||
|
DueDate: 1,
|
||||||
|
LastUpdate: 1,
|
||||||
|
MerchantInvoiceNumber: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the Skip and Number or defaults
|
||||||
|
*/
|
||||||
|
var skip = receivedObject.Skip || 0;
|
||||||
|
var number = receivedObject.number || 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available invoices as an array.
|
||||||
|
* Note that we don't pass a callback to toArray() so it instead
|
||||||
|
* returns a promise that we handle with the standard then/catch
|
||||||
|
*/
|
||||||
|
mainDB.collectionTransaction
|
||||||
|
.find(query)
|
||||||
|
.sort({LastUpdate: -1})
|
||||||
|
.skip(skip)
|
||||||
|
.limit(number)
|
||||||
|
.project(projection)
|
||||||
|
.toArray()
|
||||||
|
.then(function(invoices) {
|
||||||
|
/**
|
||||||
|
* Successful query (though it may not have found anything)
|
||||||
|
* Need to iterate through the results, getting the
|
||||||
|
* CreationDate from the _id, and doing all the renames.
|
||||||
|
*/
|
||||||
|
const renames = {
|
||||||
|
_id: 'InvoiceID',
|
||||||
|
MerchantDisplayName: 'OtherDisplayName',
|
||||||
|
MerchantSubDisplayName: 'OtherSubDisplayName',
|
||||||
|
MerchantImage: 'OtherImage'
|
||||||
|
};
|
||||||
|
_.each(invoices, function(invoice) {
|
||||||
|
invoice.CreationDate = invoice._id.getTimestamp();
|
||||||
|
apiHelpers.renameFields(invoice, renames);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Copy just the InvoiceNumber up to the top level
|
||||||
|
//
|
||||||
|
if (!_.isUndefined(invoice.MerchantInvoiceNumber)) {
|
||||||
|
invoice.MerchantInvoiceNumber =
|
||||||
|
invoice.MerchantInvoiceNumber.InvoiceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Fix any missing DueDates
|
||||||
|
//
|
||||||
|
if (_.isUndefined(invoice.DueDate)) {
|
||||||
|
invoice.DueDate = '1970-01-01T00:00:00.000Z';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: '10075',
|
||||||
|
info: 'Invoices list sent.',
|
||||||
|
count: invoices.length,
|
||||||
|
InvoiceList: invoices
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'ListInvoices successful.'
|
||||||
|
);
|
||||||
|
}).catch(function() {
|
||||||
|
/**
|
||||||
|
* Query failed, most likely from the database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '512',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
140
node_server/ComServe/hJSON/ListItems.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview List a merchant's pre-configured items for use in the POS
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/merchant_commands/list_items/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all items for the current client. This will only be successful if the
|
||||||
|
* client is an active merchant. If not they will receive an error message.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check the user is an active merchant
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
!_.isArray(existingClient.Merchant) ||
|
||||||
|
!existingClient.Merchant.length > 0 ||
|
||||||
|
existingClient.Merchant[0].MerchantStatus !== utils.MerchantStatusActive
|
||||||
|
) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '470',
|
||||||
|
info: 'Not a merchant'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the list of items from the database
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - ClientID of the item must match the current client
|
||||||
|
* - ItemStatus must be active unless we have a ModifiedSince date
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we've been given a last modified date, then we want only items
|
||||||
|
* modified since then, and we want to include all items (including
|
||||||
|
* deleted) so that an app can work out the changes.
|
||||||
|
*
|
||||||
|
* If we don't, we only want the Active items as it is just a
|
||||||
|
* basic list of items requested.
|
||||||
|
*/
|
||||||
|
if (receivedObject.ModifiedSince) {
|
||||||
|
query.LastUpdate = {
|
||||||
|
$gte: new Date(receivedObject.ModifiedSince)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
query.ItemStatus = utils.ItemStatusActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 1,
|
||||||
|
BridgeID: 1,
|
||||||
|
ItemStatus: 1,
|
||||||
|
ItemCode: 1,
|
||||||
|
Description: 1,
|
||||||
|
Tags: 1,
|
||||||
|
VATCode: 1,
|
||||||
|
VATRate: 1,
|
||||||
|
NetAmount: 1,
|
||||||
|
GrossAmount: 1,
|
||||||
|
ImageID: 1,
|
||||||
|
LoyaltyPoints: 1,
|
||||||
|
LastUpdate: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available items as an array.
|
||||||
|
* Note that we don't pass a callback to toArray() so it instead
|
||||||
|
* returns a promise that we handle with the standard then/catch
|
||||||
|
*/
|
||||||
|
mainDB.collectionItems
|
||||||
|
.find(query)
|
||||||
|
.project(projection)
|
||||||
|
.toArray()
|
||||||
|
.then(function(items) {
|
||||||
|
/**
|
||||||
|
* Successful query (though it may not have found anything)
|
||||||
|
* Need to iterate through the results, moving _id to ItemID
|
||||||
|
* before we return them
|
||||||
|
*/
|
||||||
|
_.each(items, function(item) {
|
||||||
|
item.ItemID = item._id;
|
||||||
|
delete item._id;
|
||||||
|
});
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: '10069',
|
||||||
|
info: 'Items list sent.',
|
||||||
|
ItemList: items
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'ListItems successful'
|
||||||
|
);
|
||||||
|
}).catch(function() {
|
||||||
|
/**
|
||||||
|
* Query failed, most likely from the database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '469',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
120
node_server/ComServe/hJSON/ListMessages.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview List messages for this client
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/messaging_commands/listmessages/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all messages for the current client, or just those of the Type passed in.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the list of messages from the database
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - ClientID of the message must match the current client
|
||||||
|
* - TimeFilter must be before <now>
|
||||||
|
* - Type must match the request type, if any
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
ClientID: existingClient.ClientID,
|
||||||
|
TimeFilter: {$lte: new Date()}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we've been given a Type, filter by that type
|
||||||
|
*
|
||||||
|
* If we don't, we only want the Active messages as it is just a
|
||||||
|
* basic list of messages requested.
|
||||||
|
*/
|
||||||
|
if (receivedObject.Type) {
|
||||||
|
query.Type = receivedObject.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 1,
|
||||||
|
Type: 1,
|
||||||
|
Read: 1,
|
||||||
|
Info: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the Skip and Number items
|
||||||
|
*/
|
||||||
|
var skip = receivedObject.Skip || 0;
|
||||||
|
var limit = receivedObject.Number || 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available messages as an array.
|
||||||
|
* Note that we don't pass a callback to toArray() so it instead
|
||||||
|
* returns a promise that we handle with the standard then/catch
|
||||||
|
*/
|
||||||
|
mainDB.collectionMessages
|
||||||
|
.find(query)
|
||||||
|
.skip(skip)
|
||||||
|
.limit(limit)
|
||||||
|
.project(projection)
|
||||||
|
.toArray()
|
||||||
|
.then(function(messages) {
|
||||||
|
/**
|
||||||
|
* Successful query (though it may not have found anything)
|
||||||
|
* Need to iterate through the results, moving _id to MessageID
|
||||||
|
* and adding a timestamp before we return them
|
||||||
|
*/
|
||||||
|
_.each(messages, function(message) {
|
||||||
|
message.MessageID = message._id;
|
||||||
|
message.CreationDate = message._id.getTimestamp();
|
||||||
|
delete message._id;
|
||||||
|
});
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: '10070',
|
||||||
|
info: 'Messages list sent.',
|
||||||
|
count: messages.length,
|
||||||
|
MessageList: messages
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'ListMessages successful'
|
||||||
|
);
|
||||||
|
}).catch(function() {
|
||||||
|
/**
|
||||||
|
* Query failed, most likely from the database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '478',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
94
node_server/ComServe/hJSON/LogOut1.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Log Out Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Removes the session token and requires a login before proceeding.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/logout1/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the session token.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var logoutData = {};
|
||||||
|
logoutData.ClientID = existingDevice.ClientID;
|
||||||
|
logoutData.DeviceToken = existingDevice.DeviceToken;
|
||||||
|
logoutData.SessionToken = existingDevice.SessionToken;
|
||||||
|
logoutData.SourceIP = functionInfo.remote;
|
||||||
|
logoutData.OperationType = 'LogOut';
|
||||||
|
logoutData.DateTime = timestamp;
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {_id: mongodb.ObjectID(existingDevice._id)}, {
|
||||||
|
$set: {
|
||||||
|
SessionToken: '',
|
||||||
|
SessionTokenExpiry: timestamp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '214',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store logout data.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionBridgeLogin, logoutData, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '225',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return successful logout to the user.
|
||||||
|
*/
|
||||||
|
var userName = existingClient.DisplayName;
|
||||||
|
if (userName === '') {
|
||||||
|
userName = '[New User]';
|
||||||
|
}
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10030',
|
||||||
|
info: 'LogOut1 successful.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
(userName + ' logged out.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
337
node_server/ComServe/hJSON/Login1.js
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Add Login Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Log in to the system and issues a session token.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/login1/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {!object} hmacData - hmac information {!address, !method, !body, ?timestamp, ?hmac}
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Valid login request.
|
||||||
|
* Check API version.
|
||||||
|
*/
|
||||||
|
if (receivedObject.APIVersion.split('.')[0] !== config.CCServerVersion.split('.')[0]) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '47',
|
||||||
|
info: 'A major revision change has occurred. The App must be updated.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Obsolete App version.',
|
||||||
|
('AI [' + receivedObject.ClientName + ']'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the device and check the token.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceToken: receivedObject.DeviceToken}, undefined, false,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '41',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the device was found.
|
||||||
|
*/
|
||||||
|
if (existingDevice === null) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '42',
|
||||||
|
info: 'Invalid device token.'
|
||||||
|
},
|
||||||
|
'ERROR',
|
||||||
|
'Mobile device cannot be matched to token.',
|
||||||
|
('AF [' + receivedObject.ClientName + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check device status.
|
||||||
|
*/
|
||||||
|
var currentDeviceStatus = auth.checkDeviceStatus(existingDevice.DeviceStatus);
|
||||||
|
if (currentDeviceStatus) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: currentDeviceStatus.code.toString(),
|
||||||
|
info: currentDeviceStatus.message
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
null,
|
||||||
|
('AI [' + receivedObject.ClientName + ' (' + existingDevice.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the passcode - 5 digit pin minimum.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
auth.checkDevicePIN(receivedObject.DeviceAuthorisation, existingDevice, timestamp, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
null,
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next pull the owning client based on the ID stored with the device.
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientID: existingDevice.ClientID}, undefined, false,
|
||||||
|
function(err, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '45',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User name not found.
|
||||||
|
*/
|
||||||
|
if (existingClient === null) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '47',
|
||||||
|
info: 'Invalid client e-mail.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
null,
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the email we were passed matches that of the owning client
|
||||||
|
if (receivedObject.ClientName !== existingClient.ClientName) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '46',
|
||||||
|
info: 'E-mail mismatch.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('E-mail mismatch in received data (' + receivedObject.ClientName + ').'),
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check client status.
|
||||||
|
*/
|
||||||
|
var currentClientStatus = auth.checkClientStatus(existingClient.ClientStatus);
|
||||||
|
if (currentClientStatus) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: currentClientStatus.code.toString(),
|
||||||
|
info: currentClientStatus.message
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
null,
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the HMAC.
|
||||||
|
* We have to add the ClientName into HMAC data before checking
|
||||||
|
*/
|
||||||
|
hmacData.ClientName = existingClient.ClientName;
|
||||||
|
auth.checkHMAC(existingDevice, hmacData, 'Login1.process', function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
null,
|
||||||
|
(existingDevice.ClientID + ' (' + existingDevice.DeviceNumber + ')'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the last login location if available.
|
||||||
|
*/
|
||||||
|
var newLastLoginLocation = null;
|
||||||
|
if ((receivedObject.Longitude !== null) && (receivedObject.Latitude !== null)) {
|
||||||
|
newLastLoginLocation = {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [receivedObject.Longitude, receivedObject.Latitude]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the session token and store a successful login.
|
||||||
|
*/
|
||||||
|
var newExpiry = new Date(timestamp);
|
||||||
|
newExpiry.setMinutes(newExpiry.getMinutes() + utils.sessionTimeout);
|
||||||
|
var newDeviceHardware = receivedObject.DeviceHardware;
|
||||||
|
var newDeviceSoftware = receivedObject.DeviceSoftware;
|
||||||
|
var sessionToken = utils.randomCode(utils.fullAlphaNumeric, utils.tokenLength);
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: receivedObject.DeviceToken}, {
|
||||||
|
$set: {
|
||||||
|
DeviceHardware: newDeviceHardware,
|
||||||
|
DeviceSoftware: newDeviceSoftware,
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
SessionTokenExpiry: newExpiry,
|
||||||
|
LastLoginLocation: newLastLoginLocation,
|
||||||
|
LastLoginIP: functionInfo.remote,
|
||||||
|
LastLogin: timestamp,
|
||||||
|
LoginAttempts: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '49',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the login time and location.
|
||||||
|
*/
|
||||||
|
var loginData = {};
|
||||||
|
loginData.ClientID = existingClient.ClientID;
|
||||||
|
loginData.DeviceToken = existingDevice.DeviceToken;
|
||||||
|
loginData.SessionToken = sessionToken;
|
||||||
|
loginData.SourceLocation = newLastLoginLocation;
|
||||||
|
loginData.SourceIP = functionInfo.remote;
|
||||||
|
loginData.OperationType = 'Login';
|
||||||
|
loginData.ServerVersion = config.CCServerVersion;
|
||||||
|
loginData.APIVersion = receivedObject.APIVersion;
|
||||||
|
loginData.DeviceHardware = receivedObject.DeviceHardware;
|
||||||
|
loginData.DeviceSoftware = receivedObject.DeviceSoftware;
|
||||||
|
loginData.DateTime = timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store login data.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionBridgeLogin, loginData, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '194',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status bits.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var AcceptEULA = 0;
|
||||||
|
if (config.EULAVersion !== existingClient.EULAVersionAccepted) {
|
||||||
|
AcceptEULA = 1;
|
||||||
|
}
|
||||||
|
var clientDetailsSet = 0;
|
||||||
|
if (utils.bitsAllSet(existingClient.ClientStatus, utils.ClientDetailsMask)) {
|
||||||
|
clientDetailsSet = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble strings and objects for response.
|
||||||
|
*/
|
||||||
|
var userName = existingClient.DisplayName;
|
||||||
|
if (userName === '') {
|
||||||
|
userName = '[New User]';
|
||||||
|
}
|
||||||
|
var toSend = {
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
DeviceName: existingDevice.DeviceName,
|
||||||
|
timeout: utils.sessionTimeout,
|
||||||
|
PayCodeTimeout: utils.payCodeTimeout,
|
||||||
|
CallTimeout: config.callTimeout,
|
||||||
|
MerchantStatus: existingClient.Merchant[0].MerchantStatus,
|
||||||
|
PollingInterval: utils.pollingInterval,
|
||||||
|
AcceptEULA: AcceptEULA,
|
||||||
|
ServerVersion: config.CCServerVersion,
|
||||||
|
PaymentMin: utils.paymentMin,
|
||||||
|
PaymentMax: utils.paymentMax,
|
||||||
|
TipMin: utils.tipMin,
|
||||||
|
TipMax: utils.tipMax,
|
||||||
|
TransactionMin: utils.transactionMin,
|
||||||
|
ClientDetailsSet: clientDetailsSet,
|
||||||
|
DesyncThreshold: config.HMACDesyncThreshold,
|
||||||
|
FeatureFlags: existingClient.FeatureFlags
|
||||||
|
};
|
||||||
|
if (existingDevice.PendingHMAC !== '') {
|
||||||
|
toSend.PendingHMAC = existingDevice.PendingHMAC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the Client on first login.
|
||||||
|
*/
|
||||||
|
if (existingClient.FirstLogin === 1) {
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {'ClientName': existingClient.ClientName}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
FirstLogin: 0
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '102',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login success (first login).
|
||||||
|
*/
|
||||||
|
toSend.code = '10010';
|
||||||
|
toSend.info = 'Login1 first login successful.';
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
toSend,
|
||||||
|
'INFO',
|
||||||
|
(userName + ' first log in.'));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login success (not first login).
|
||||||
|
*/
|
||||||
|
toSend.code = '10027';
|
||||||
|
toSend.info = 'Login1 successful.';
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
toSend,
|
||||||
|
'INFO',
|
||||||
|
(userName + ' logged in.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
128
node_server/ComServe/hJSON/MarkMessage.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Allows a message to be marked as read, unread, etc.
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/messaging_commands/markmessage/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var Q = require('q');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows a message to be marked as read, unread, etc.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the message as the requested type
|
||||||
|
*
|
||||||
|
* Build the query. The limits are:
|
||||||
|
* - ClientID must be me
|
||||||
|
* - MessageID must match the _id of the message
|
||||||
|
* - TimeFilter must be before <now>
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
ClientID: existingClient.ClientID,
|
||||||
|
_id: mongodb.ObjectID(receivedObject.MessageID),
|
||||||
|
TimeFilter: {$lte: new Date()}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the update
|
||||||
|
*/
|
||||||
|
var update = {
|
||||||
|
$currentDate: {
|
||||||
|
LastUpdate: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (receivedObject.Mark === 'Read') {
|
||||||
|
// Mark as read by setting the read date
|
||||||
|
update.$currentDate.Read = true;
|
||||||
|
} else if (receivedObject.Mark === 'Unread') {
|
||||||
|
// Mark as unread by clearing the read date
|
||||||
|
update.$set = {
|
||||||
|
Read: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the options.
|
||||||
|
*/
|
||||||
|
var options = {
|
||||||
|
upsert: false,
|
||||||
|
multi: false,
|
||||||
|
comment: 'MarkMessage' // For profiler logs use
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the object
|
||||||
|
*/
|
||||||
|
Q.nfcall(
|
||||||
|
mainDB.updateObject,
|
||||||
|
mainDB.collectionMessages,
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
options,
|
||||||
|
false
|
||||||
|
).then(function(result) {
|
||||||
|
/**
|
||||||
|
* Successful query (though it may not have found anything to update)
|
||||||
|
*/
|
||||||
|
if (result.result.n === 1) {
|
||||||
|
/**
|
||||||
|
* A document was updated, so this is total success
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10072',
|
||||||
|
info: 'MarkMessage successful'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('MarkMessage successful: ' + receivedObject.MessageID));
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* The request ran, but didn't find any documents
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '484',
|
||||||
|
info: 'Invalid MessageID.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
('Invalid message ID: ' + receivedObject.MessageID));
|
||||||
|
}
|
||||||
|
}).catch(function() {
|
||||||
|
/**
|
||||||
|
* Query failed, most likely from the database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '483',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
242
node_server/ComServe/hJSON/PINReset.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js PIN Reset Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Resets the device PIN. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/pinreset/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var templates = require(global.pathPrefix + '../utils/templates.js');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject) {
|
||||||
|
/**
|
||||||
|
* Valid PIN reset request. Find the e-mail address.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientName: receivedObject.ClientName}, undefined, false, function(err, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '129',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User name not found if null.
|
||||||
|
*/
|
||||||
|
if (!existingClient) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '130',
|
||||||
|
info: 'E-mail mismatch.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'E-mail not found in database.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check client status.
|
||||||
|
*/
|
||||||
|
var currentClientStatus = auth.checkClientStatus(existingClient.ClientStatus);
|
||||||
|
if (currentClientStatus) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: currentClientStatus.code.toString(),
|
||||||
|
info: currentClientStatus.message
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the password.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
auth.checkClientPassword(receivedObject.Password, existingClient, timestamp, function(err) {
|
||||||
|
/**
|
||||||
|
* Check if there was an error processing the password.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid reset request. Find the device again and verify the token.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceNumber: receivedObject.DeviceNumber}, undefined, false,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '133',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device not found.
|
||||||
|
*/
|
||||||
|
if (!existingDevice) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '134',
|
||||||
|
info: 'Device number not found.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check device ID matches.
|
||||||
|
*/
|
||||||
|
if (receivedObject.DeviceUuid !== existingDevice.DeviceUuid) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '135',
|
||||||
|
info: 'Invalid device ID.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check device is associated with e-mail.
|
||||||
|
*/
|
||||||
|
if (existingDevice.ClientID !== existingClient.ClientID) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '136',
|
||||||
|
info: 'ClientName mismatch.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device does not belong to Client.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check device status.
|
||||||
|
*/
|
||||||
|
var currentDeviceStatus = auth.checkDeviceStatus(existingDevice.DeviceStatus);
|
||||||
|
if (currentDeviceStatus) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: currentDeviceStatus.code.toString(),
|
||||||
|
info: currentDeviceStatus.message
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All checks complete. Reset the PIN.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
auth.encryptPBKDF2(receivedObject.DeviceAuthorisation, function(err, newSalt, newHash) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '137',
|
||||||
|
info: 'Encryption error.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the database.
|
||||||
|
*/
|
||||||
|
var newDeviceAuthorisation = config.pinCryptoVersion + '::' + newHash;
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceNumber: receivedObject.DeviceNumber}, {
|
||||||
|
$set: {
|
||||||
|
DeviceSalt: newSalt,
|
||||||
|
DeviceAuthorisation: newDeviceAuthorisation,
|
||||||
|
LoginAttempts: 0,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '138',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PIN updated. Confirm by e-mail.
|
||||||
|
*/
|
||||||
|
var geolocation = '';
|
||||||
|
var geoTag = 'Lat/Long';
|
||||||
|
if ((receivedObject.Latitude === null) ||
|
||||||
|
(receivedObject.Longitude === null)) {
|
||||||
|
geoTag = 'No GPS information available.';
|
||||||
|
} else {
|
||||||
|
geolocation = 'http://maps.google.com/maps?q=loc:' + receivedObject.Latitude +
|
||||||
|
',' + receivedObject.Longitude;
|
||||||
|
}
|
||||||
|
var htmlEmail = templates.render('pin-reset', {
|
||||||
|
LatLong: geolocation,
|
||||||
|
GeoTag: geoTag,
|
||||||
|
DeviceNumber: receivedObject.DeviceNumber
|
||||||
|
});
|
||||||
|
mailer.sendEmail(null, receivedObject.ClientName, 'Bridge PIN Reset', htmlEmail, 'PINReset.process',
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '139',
|
||||||
|
info: 'Unable to send e-mail.'
|
||||||
|
},
|
||||||
|
'ERROR', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PIN Reset successful.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10014',
|
||||||
|
info: 'PIN reset successful.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('PIN reset successful at Lat/Long [' + receivedObject.Latitude + ',' +
|
||||||
|
receivedObject.Longitude + '].'),
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
352
node_server/ComServe/hJSON/PayCodeRequest.js
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js PayCode Request Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2015 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Gets a limited time PayCode that can be given to the merchant.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/payment_commands/paycoderequest/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes.
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var async = require('async');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that display names are valid. Otherwise, do not allow the Client to continue.
|
||||||
|
*/
|
||||||
|
if (utils.MinDisplayNameLength > existingClient.DisplayName.length) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '472',
|
||||||
|
info: 'DisplayName is invalid. Please fill out customer details.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((utils.MinDisplayNameLength > existingClient.Merchant[0].CompanyAlias.length) &&
|
||||||
|
(existingClient.Merchant[0].MerchantStatus === utils.MerchantStatusActive)) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '473',
|
||||||
|
info: 'CompanyAlias is invalid. Please fill out Merchant details.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up paycode to write.
|
||||||
|
*/
|
||||||
|
var newPayCode = {};
|
||||||
|
newPayCode.DeviceToken = existingDevice.DeviceToken;
|
||||||
|
newPayCode.SessionToken = receivedObject.SessionToken;
|
||||||
|
var timestamp = null;
|
||||||
|
var payCodeLength = 5;
|
||||||
|
var counter = 5;
|
||||||
|
var notWritten = true;
|
||||||
|
var payCodeObject = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep trying to issue a PayCode.
|
||||||
|
*/
|
||||||
|
async.doWhilst(
|
||||||
|
/**
|
||||||
|
* Keep doing this.
|
||||||
|
*/
|
||||||
|
function(callback) {
|
||||||
|
counter = counter - 1;
|
||||||
|
newPayCode.PayCode = utils.payCodeGeneration(utils.paycodeString, payCodeLength, 'Bridge');
|
||||||
|
timestamp = new Date();
|
||||||
|
newPayCode.Creation = new Date(timestamp);
|
||||||
|
newPayCode.Expiry = new Date(timestamp);
|
||||||
|
newPayCode.Expiry.setMinutes(newPayCode.Expiry.getMinutes() + utils.payCodeTimeout);
|
||||||
|
newPayCode.TransactionID = null;
|
||||||
|
mainDB.addObject(mainDB.collectionPayCode, newPayCode, undefined, true, function(err, payCodeAdded) {
|
||||||
|
/**
|
||||||
|
* If the payCodeAdded is returned then the PayCode is unique and has been added to the DB.
|
||||||
|
*/
|
||||||
|
if (payCodeAdded) {
|
||||||
|
notWritten = false;
|
||||||
|
payCodeObject = payCodeAdded[0];
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Until this.
|
||||||
|
*/
|
||||||
|
function() {
|
||||||
|
/**
|
||||||
|
* Attempt to write 5 paycodes of the same length.
|
||||||
|
*/
|
||||||
|
if (counter < 1) {
|
||||||
|
counter = 5;
|
||||||
|
payCodeLength = payCodeLength + 1;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Paycode has gotten too long. Return an error.
|
||||||
|
*/
|
||||||
|
if (payCodeLength > 12) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return notWritten;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Then do this!
|
||||||
|
*/
|
||||||
|
function(err) {
|
||||||
|
/**
|
||||||
|
* 5 seconds have passed.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '157',
|
||||||
|
info: 'Cannot issue a PayCode.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a unique paycode has been issued.
|
||||||
|
*/
|
||||||
|
if (payCodeLength > 12) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '158',
|
||||||
|
info: 'Cannot generate a unique PayCode.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the new transaction.
|
||||||
|
*/
|
||||||
|
var newTrans = mainDB.blankTransaction();
|
||||||
|
newTrans.PayCode = payCodeObject.PayCode;
|
||||||
|
newTrans.PayCodeID = payCodeObject._id;
|
||||||
|
newTrans.PayCodeExpiry = payCodeObject.Expiry;
|
||||||
|
newTrans.CustomerDeviceToken = receivedObject.DeviceToken;
|
||||||
|
newTrans.CustomerSessionToken = receivedObject.SessionToken;
|
||||||
|
newTrans.CustomerAccountID = receivedObject.AccountID;
|
||||||
|
newTrans.CustomerClientID = existingClient.ClientID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account information and fill in transaction.
|
||||||
|
*
|
||||||
|
* Need to ignore jshint warnings here:
|
||||||
|
* 1. Cyclomatic complexity too high - W074. Legacy code.
|
||||||
|
*/
|
||||||
|
//jshint -W074
|
||||||
|
mainDB.findOneObject(mainDB.collectionAccount, {_id: mongodb.ObjectID(receivedObject.AccountID)}, undefined, false,
|
||||||
|
function(err, existingCustomerAccount) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '229',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that we got an account back.
|
||||||
|
*/
|
||||||
|
if (!existingCustomerAccount) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '274',
|
||||||
|
info: 'Invalid customer AccountID.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//jshint -W016
|
||||||
|
/**
|
||||||
|
* Check that this is not a deleted account. Uses bitwise comparison.
|
||||||
|
*
|
||||||
|
* Need to ignore jshint warnings here:
|
||||||
|
* 1. Unexpected bitwise comparison. This is deliberately a bitwise comparison.
|
||||||
|
*/
|
||||||
|
if (existingCustomerAccount.AccountStatus & utils.AccountDeleted) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '273',
|
||||||
|
info: 'Deleted customer AccountID.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that this is not an API generated account (as they
|
||||||
|
* don't have the encrypted info we need to process a transaction)
|
||||||
|
* Uses bitwise comparison.
|
||||||
|
*
|
||||||
|
* Need to ignore jshint warnings here:
|
||||||
|
* 1. Unexpected bitwise comparison. This is deliberately a bitwise comparison.
|
||||||
|
*/
|
||||||
|
if (existingCustomerAccount.AccountStatus & utils.AccountApiCreated) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '557',
|
||||||
|
info: 'Unsupported account type.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//jshint +W016
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operational account. Check that there is a valid billing address.
|
||||||
|
*/
|
||||||
|
if (existingCustomerAccount.BillingAddress === '') {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '490',
|
||||||
|
info: 'No valid billing address.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure it's a payments account.
|
||||||
|
*/
|
||||||
|
if (existingCustomerAccount.PaymentsAccount !== 1) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '295',
|
||||||
|
info: 'Not a payments account.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill in account details.
|
||||||
|
*/
|
||||||
|
switch (existingCustomerAccount.UserImage) {
|
||||||
|
case 'Selfie':
|
||||||
|
newTrans.CustomerDisplayName = existingClient.DisplayName;
|
||||||
|
newTrans.CustomerImage = existingClient.Selfie;
|
||||||
|
break;
|
||||||
|
case 'defaultSelfie':
|
||||||
|
newTrans.CustomerDisplayName = existingClient.DisplayName;
|
||||||
|
newTrans.CustomerImage = config.defaultSelfie;
|
||||||
|
break;
|
||||||
|
case 'CompanyLogo0':
|
||||||
|
newTrans.CustomerDisplayName = existingClient.Merchant[0].CompanyAlias;
|
||||||
|
newTrans.CustomerSubDisplayName = existingClient.Merchant[0].CompanySubName;
|
||||||
|
newTrans.CustomerImage = existingClient.Merchant[0].CompanyLogo;
|
||||||
|
if (existingClient.Merchant[0].VATNo) {
|
||||||
|
newTrans.CustomerVATNo = existingClient.Merchant[0].VATNo;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'defaultCompanyLogo0':
|
||||||
|
newTrans.CustomerDisplayName = existingClient.Merchant[0].CompanyAlias;
|
||||||
|
newTrans.CustomerSubDisplayName = existingClient.Merchant[0].CompanySubName;
|
||||||
|
newTrans.CustomerImage = config.defaultCompanyLogo0;
|
||||||
|
if (existingClient.Merchant[0].VATNo) {
|
||||||
|
newTrans.CustomerVATNo = existingClient.Merchant[0].VATNo;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/**
|
||||||
|
* Error condition.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '230',
|
||||||
|
info: 'Invalid image details.'
|
||||||
|
},
|
||||||
|
'ERROR',
|
||||||
|
('The UserImage is invalid for AccountID ' + receivedObject.AccountID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newTrans.StatusInfo = 'Paycode issued. Waiting for merchant...';
|
||||||
|
if ((receivedObject.Longitude !== null) && (receivedObject.Latitude !== null)) {
|
||||||
|
newTrans.CustomerLocation = {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [receivedObject.Longitude, receivedObject.Latitude]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
newTrans.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the transaction and get the object ID back.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionTransaction, newTrans, undefined, false, function(err, transactionAdded) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '159',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transactions successfully added. Update the PayCode.
|
||||||
|
*/
|
||||||
|
var transaction = mongodb.ObjectID(transactionAdded[0]._id).toString();
|
||||||
|
mainDB.updateObject(
|
||||||
|
mainDB.collectionPayCode,
|
||||||
|
{_id: mongodb.ObjectID(payCodeObject._id)},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
TransactionID: transaction
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '160',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return code to user
|
||||||
|
*/
|
||||||
|
var timeNow = new Date();
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10015',
|
||||||
|
info: 'Paycode issued.',
|
||||||
|
paycode: newPayCode.PayCode,
|
||||||
|
creation: newPayCode.Creation,
|
||||||
|
now: timeNow,
|
||||||
|
expiry: newPayCode.Expiry,
|
||||||
|
transactionID: transactionAdded[0]._id
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('New PayCode ' + newPayCode.PayCode + ' issued.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
61
node_server/ComServe/hJSON/PostCodeLookup.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview List the addresses that match the given postcode
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/utility_commands/postcode_lookup/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var postcodeUtils = require(global.pathPrefix + '../utils/postcodes.js');
|
||||||
|
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var apiHelpers = require(global.pathPrefix + '../utils/api_helpers.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List addresses for the current postcode.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookupP = postcodeUtils.postcodeLookup(receivedObject.PostCode);
|
||||||
|
lookupP.then((addresses) => {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: '10078',
|
||||||
|
info: 'Address list returned.',
|
||||||
|
AddressCount: addresses.length,
|
||||||
|
Addresses: addresses
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'PostCodeLookup successful.'
|
||||||
|
);
|
||||||
|
}).catch((error) => {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '531',
|
||||||
|
info: 'Unable to lookup addresses.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}); // End auth.validSession callback
|
||||||
|
};
|
87
node_server/ComServe/hJSON/RedeemPayCode.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Redeem PayCode Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Allows a merchant to add their details to a transaction.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/payment_commands/redeempaycode/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
const authP = require(global.pathPrefix + 'auth-promises.js');
|
||||||
|
const impl = require(global.pathPrefix + '../impl/redeem_paycode.js');
|
||||||
|
const Q = require('q');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = async function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
let authDetails = null;
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
authDetails = await authP.validSession(
|
||||||
|
res,
|
||||||
|
receivedObject.DeviceToken,
|
||||||
|
receivedObject.SessionToken,
|
||||||
|
functionInfo,
|
||||||
|
hmacData
|
||||||
|
).then((result) => {
|
||||||
|
return {
|
||||||
|
existingDevice: result[0],
|
||||||
|
existingClient: result[1]
|
||||||
|
};
|
||||||
|
}).catch(() => Q.reject()); // Error has been handled in auth.ValidSession
|
||||||
|
|
||||||
|
const response = await impl.redeemPaycodeP(
|
||||||
|
authDetails.existingClient,
|
||||||
|
receivedObject);
|
||||||
|
|
||||||
|
authP.respond(res, 200, authDetails.existingDevice, hmacData, functionInfo,
|
||||||
|
response,
|
||||||
|
'INFO',
|
||||||
|
('PayCode ' + receivedObject.PayCode + ' redeemed for TransactionID ' +
|
||||||
|
response.TransactionID + '.'));
|
||||||
|
} catch (error) {
|
||||||
|
if (error) {
|
||||||
|
//
|
||||||
|
// Check if any of these errors need to be logged
|
||||||
|
//
|
||||||
|
let logType = null;
|
||||||
|
const warnings = [
|
||||||
|
'174',
|
||||||
|
'474',
|
||||||
|
'475',
|
||||||
|
'476',
|
||||||
|
'176',
|
||||||
|
'179',
|
||||||
|
'276',
|
||||||
|
'491',
|
||||||
|
'275',
|
||||||
|
'296',
|
||||||
|
'231'
|
||||||
|
];
|
||||||
|
if (warnings.indexOf(error.code) !== -1) {
|
||||||
|
logType = 'WARNING';
|
||||||
|
}
|
||||||
|
authP.respond(res, 200, authDetails.existingDevice, hmacData, functionInfo,
|
||||||
|
{
|
||||||
|
code: error.code,
|
||||||
|
info: error.info
|
||||||
|
},
|
||||||
|
logType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
483
node_server/ComServe/hJSON/RefundTransaction.js
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Refund Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Refunds the transaction to the customer. This function should only be used if the full transaction is
|
||||||
|
* being refunded in one go. There is (will) be a separate transaction for partial refunds.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/payment_commands/refundtransaction/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var worldpay = require(global.pathPrefix + 'worldpay.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var async = require('async');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are local variables here that must be available throughout the function.
|
||||||
|
*/
|
||||||
|
var newinfo = '';
|
||||||
|
var locals = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call all database updates in series.
|
||||||
|
*/
|
||||||
|
async.series([
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Find the transaction to refund.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionTransaction,
|
||||||
|
{_id: mongodb.ObjectID(receivedObject.TransactionID)}, undefined, false, function(err, existingTransaction) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: {code: '228', info: 'Database offline.'}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No transaction in the database.
|
||||||
|
*/
|
||||||
|
if (!existingTransaction) {
|
||||||
|
callback({
|
||||||
|
data: {code: '237', info: 'Invalid TransactionID.'},
|
||||||
|
logType: 'WARNING'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only transactions with a status of 3 can be fully refunded.
|
||||||
|
* Partial refunds may work on other transactions.
|
||||||
|
*/
|
||||||
|
if (existingTransaction.TransactionStatus !== 3) {
|
||||||
|
if (existingTransaction.TransactionStatus === 4) {
|
||||||
|
callback({
|
||||||
|
data: {code: '235', info: 'Already refunded.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: 'Transaction already fully refunded.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
callback({
|
||||||
|
data: {code: '236', info: 'Cannot be refunded.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: 'Cannot be refunded (partial refund may still work).'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure this is the merchant.
|
||||||
|
*/
|
||||||
|
if (existingTransaction.MerchantClientID !== existingClient.ClientID) {
|
||||||
|
callback({
|
||||||
|
data: {code: '238', info: 'Invalid ClientName.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: 'Transaction can only be refunded by the merchant.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the transaction data.
|
||||||
|
*/
|
||||||
|
locals.existingTransaction = existingTransaction;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Update the transaction.
|
||||||
|
*/
|
||||||
|
locals.succeeded = false;
|
||||||
|
if (locals.existingTransaction.AcquirerName === 'Demo') {
|
||||||
|
/**
|
||||||
|
* Refund the Demo transaction.
|
||||||
|
*/
|
||||||
|
locals.succeeded = true;
|
||||||
|
locals.newLastUpdate = new Date();
|
||||||
|
locals.newStatusInfo = 'Refunded. Authorisation code ' + utils.timeBasedRandomCode() + '.';
|
||||||
|
locals.newTransactionStatus = utils.TransactionStatus.REFUNDED;
|
||||||
|
locals.newAmountRefunded = locals.existingTransaction.TotalAmount;
|
||||||
|
callback();
|
||||||
|
} else if (locals.existingTransaction.AcquirerName === 'Worldpay') {
|
||||||
|
/**
|
||||||
|
* This is a Worldpay transaction refund.
|
||||||
|
*/
|
||||||
|
locals.worldpayServiceKey = utils.decryptDataV1(locals.existingTransaction.AcquirerCipher);
|
||||||
|
if (typeof locals.worldpayServiceKey === 'object') {
|
||||||
|
callback({
|
||||||
|
data: {code: '561', info: 'Error decrypting Worldpay service key.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: 'Transaction ' + locals.existingTransaction._id.toString() + ': ' + locals.worldpayServiceKey
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the refund function.
|
||||||
|
*/
|
||||||
|
worldpay.worldpayFunction(
|
||||||
|
'POST',
|
||||||
|
'orders/' + locals.existingTransaction.SaleReference + '/refund',
|
||||||
|
locals.worldpayServiceKey,
|
||||||
|
null, // No additional headers.
|
||||||
|
{},
|
||||||
|
function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
console.log('ERR:' + JSON.stringify(err));
|
||||||
|
}
|
||||||
|
if (response) {
|
||||||
|
console.log('RESPONSE: ' + response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: {code: '260', info: err.message}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
locals.succeeded = true;
|
||||||
|
locals.newLastUpdate = new Date();
|
||||||
|
locals.newStatusInfo = 'Refunded. Worldpay.';
|
||||||
|
locals.newTransactionStatus = utils.TransactionStatus.REFUNDED;
|
||||||
|
locals.newAmountRefunded = locals.existingTransaction.TotalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Invalid acquiring bank.
|
||||||
|
*/
|
||||||
|
newinfo = 'Invalid acquiring bank (' + locals.existingTransaction.AcquirerName + ').';
|
||||||
|
callback({
|
||||||
|
data: {code: '242', info: 'Invalid acquirer.'},
|
||||||
|
logType: 'ERROR',
|
||||||
|
altString: newinfo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Update the transaction.
|
||||||
|
*/
|
||||||
|
mainDB.collectionTransaction.findOneAndUpdate({
|
||||||
|
_id: mongodb.ObjectID(receivedObject.TransactionID),
|
||||||
|
MerchantClientID: existingClient.ClientID,
|
||||||
|
TransactionStatus: utils.TransactionStatus.COMPLETE
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
AmountRefunded: locals.newAmountRefunded,
|
||||||
|
StatusInfo: locals.newStatusInfo,
|
||||||
|
TransactionStatus: locals.newTransactionStatus,
|
||||||
|
LastUpdate: locals.newLastUpdate
|
||||||
|
},
|
||||||
|
$inc: {
|
||||||
|
LastVersion: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
upsert: false
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: {code: '260', info: 'Database offline.'}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the payment was a success.
|
||||||
|
*/
|
||||||
|
if (!locals.succeeded) {
|
||||||
|
newinfo = 'Refund failed to ' + locals.existingTransaction.CustomerClientID + '.';
|
||||||
|
callback({
|
||||||
|
data: {code: '261', info: 'Refund failed.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: newinfo
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success. Call back accordingly.
|
||||||
|
*/
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Set up new history items to reverse the transaction.
|
||||||
|
*/
|
||||||
|
locals.newCustomerHist = mainDB.blankTransactionHistory();
|
||||||
|
locals.newCustomerHist.TransactionID = receivedObject.TransactionID;
|
||||||
|
locals.newCustomerHist.TransactionType = 3;
|
||||||
|
locals.newCustomerHist.AccountID = locals.existingTransaction.CustomerAccountID;
|
||||||
|
locals.newCustomerHist.ClientID = locals.existingTransaction.CustomerClientID;
|
||||||
|
locals.newCustomerHist.OtherDisplayName = locals.existingTransaction.MerchantDisplayName;
|
||||||
|
locals.newCustomerHist.OtherSubDisplayName = locals.existingTransaction.MerchantSubDisplayName;
|
||||||
|
locals.newCustomerHist.OtherImage = locals.existingTransaction.MerchantImage;
|
||||||
|
locals.newCustomerHist.TotalAmount = locals.existingTransaction.TotalAmount;
|
||||||
|
locals.newCustomerHist.SaleTime = locals.newLastUpdate;
|
||||||
|
locals.newCustomerHist.LastUpdate = locals.newLastUpdate;
|
||||||
|
if (!_.isUndefined(locals.existingTransaction.MerchantInvoiceNumber)) {
|
||||||
|
locals.newCustomerHist.MerchantInvoiceNumber = locals.existingTransaction.MerchantInvoiceNumber;
|
||||||
|
}
|
||||||
|
locals.newMerchantHist = mainDB.blankTransactionHistory();
|
||||||
|
locals.newMerchantHist.TransactionID = receivedObject.TransactionID;
|
||||||
|
locals.newMerchantHist.TransactionType = 2;
|
||||||
|
locals.newMerchantHist.AccountID = locals.existingTransaction.MerchantAccountID;
|
||||||
|
locals.newMerchantHist.ClientID = locals.existingTransaction.MerchantClientID;
|
||||||
|
locals.newMerchantHist.OtherDisplayName = locals.existingTransaction.CustomerDisplayName;
|
||||||
|
locals.newMerchantHist.OtherSubDisplayName = locals.existingTransaction.CustomerSubDisplayName;
|
||||||
|
locals.newMerchantHist.OtherImage = locals.existingTransaction.CustomerImage;
|
||||||
|
if ((receivedObject.Longitude !== null) && (receivedObject.Latitude !== null)) {
|
||||||
|
locals.newMerchantHist.MyLocation = {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [receivedObject.Longitude, receivedObject.Latitude]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
locals.newMerchantHist.TotalAmount = locals.existingTransaction.TotalAmount;
|
||||||
|
locals.newMerchantHist.SaleTime = locals.newLastUpdate;
|
||||||
|
locals.newMerchantHist.LastUpdate = locals.newCustomerHist.LastUpdate;
|
||||||
|
if (!_.isUndefined(locals.existingTransaction.MerchantInvoiceNumber)) {
|
||||||
|
locals.newMerchantHist.MerchantInvoiceNumber = locals.existingTransaction.MerchantInvoiceNumber;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Call in the customer account details.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAccount,
|
||||||
|
{_id: mongodb.ObjectID(locals.existingTransaction.CustomerAccountID)}, undefined, false,
|
||||||
|
function(err, existingCustomerAccount) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: {code: '263', info: 'Database offline.'}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that we got an account back. Note that deleted is irrelevant here as we can
|
||||||
|
* refund into a deleted account.
|
||||||
|
*/
|
||||||
|
if (!existingCustomerAccount) {
|
||||||
|
newinfo = 'Refund succeeded but cannot find customer account for ' +
|
||||||
|
locals.existingTransaction.CustomerClientID + '.';
|
||||||
|
callback({
|
||||||
|
data: {code: '264', info: 'Refund successful. No customer account.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: newinfo
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the transaction data.
|
||||||
|
*/
|
||||||
|
locals.existingCustomerAccount = existingCustomerAccount;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Process the transaction - call the merchant account details in.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAccount,
|
||||||
|
{_id: mongodb.ObjectID(locals.existingTransaction.MerchantAccountID)}, undefined, false,
|
||||||
|
function(err, existingMerchantAccount) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: {code: '263', info: 'Database offline.'}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that we got an account back. Note that deleted is irrelevant here as we
|
||||||
|
* can refund from a deleted account.
|
||||||
|
*/
|
||||||
|
if (!existingMerchantAccount) {
|
||||||
|
newinfo = 'Refund succeeded but cannot find merchant account for ' +
|
||||||
|
locals.existingTransaction.MerchantClientID + '.';
|
||||||
|
callback({
|
||||||
|
data: {code: '265', info: 'Refund successful. No merchant account.'},
|
||||||
|
logType: 'WARNING',
|
||||||
|
altString: newinfo
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the transaction data.
|
||||||
|
*/
|
||||||
|
locals.existingMerchantAccount = existingMerchantAccount;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Update the customer account if appropriate.
|
||||||
|
*/
|
||||||
|
if (locals.existingCustomerAccount.BalanceAvailable !== 0) {
|
||||||
|
locals.newBalance = locals.existingCustomerAccount.Balance + locals.existingTransaction.TotalAmount;
|
||||||
|
locals.newTransactionTotal = locals.existingCustomerAccount.TransactionTotal +
|
||||||
|
locals.existingTransaction.TotalAmount;
|
||||||
|
locals.newTotalDeposits = locals.existingCustomerAccount.TotalDeposits + locals.existingTransaction.TotalAmount;
|
||||||
|
mainDB.updateObject(mainDB.collectionAccount,
|
||||||
|
{_id: mongodb.ObjectID(locals.existingTransaction.CustomerAccountID)}, {
|
||||||
|
$set: {
|
||||||
|
TransactionTotal: locals.newTransactionTotal,
|
||||||
|
TotalDeposits: locals.newTotalDeposits,
|
||||||
|
Balance: locals.newBalance,
|
||||||
|
LastUpdate: locals.newLastUpdate
|
||||||
|
},
|
||||||
|
$inc: {
|
||||||
|
LastVersion: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: utils.createError('266', 'Database offline')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Update the merchant account.
|
||||||
|
*/
|
||||||
|
if (locals.existingMerchantAccount.BalanceAvailable !== 0) {
|
||||||
|
locals.newBalance = locals.existingMerchantAccount.Balance - locals.existingTransaction.TotalAmount;
|
||||||
|
locals.newTransactionTotal = locals.existingMerchantAccount.TransactionTotal +
|
||||||
|
locals.existingTransaction.TotalAmount;
|
||||||
|
locals.newTotalWithdrawals = locals.existingMerchantAccount.TotalWithdrawals +
|
||||||
|
locals.existingTransaction.TotalAmount;
|
||||||
|
mainDB.updateObject(mainDB.collectionAccount,
|
||||||
|
{_id: mongodb.ObjectID(locals.existingTransaction.MerchantAccountID)}, {
|
||||||
|
$set: {
|
||||||
|
TransactionTotal: locals.newTransactionTotal,
|
||||||
|
TotalWithdrawals: locals.newTotalWithdrawals,
|
||||||
|
Balance: locals.newBalance,
|
||||||
|
LastUpdate: locals.newLastUpdate
|
||||||
|
},
|
||||||
|
$inc: {
|
||||||
|
LastVersion: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: utils.createError('267', 'Database offline')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Add the customer transaction history.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionTransactionHistory, locals.newCustomerHist, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: utils.createError('268', 'Database offline')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(callback) {
|
||||||
|
/**
|
||||||
|
* Add the merchant transaction history.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionTransactionHistory, locals.newMerchantHist, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback({
|
||||||
|
data: utils.createError('269', 'Database offline')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* Callback for information. Note that err can contain 3 elements: data (JSON to return), logType, and altString.
|
||||||
|
*/
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
if (err.hasOwnProperty('logType')) {
|
||||||
|
//jshint -W117
|
||||||
|
if (err.hasOwnProperty('altString')) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, err.data, err.logType, err.altString);
|
||||||
|
} else {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, err.data, err.logType);
|
||||||
|
}
|
||||||
|
//jshint +W117
|
||||||
|
} else {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, err.data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete success.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10035',
|
||||||
|
info: 'Refund confirmed.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Refund confirmed by acquirer to ' + locals.existingTransaction.CustomerClientID + '.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
268
node_server/ComServe/hJSON/Register1.js
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Register 1 Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* First registration command. Creates Client and Device database entries. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/register1/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var templates = require(global.pathPrefix + '../utils/templates.js');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var sms = require(global.pathPrefix + 'sms.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject) {
|
||||||
|
/**
|
||||||
|
* Local variables.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid registration request.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Registration request received.',
|
||||||
|
'Register1.process',
|
||||||
|
'',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next verify that the e-mail address has not been used before.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientName: receivedObject.ClientName}, undefined, false, function(err, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '3',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client info retrieved if present. Check for the device.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice,
|
||||||
|
{DeviceNumber: receivedObject.DeviceNumber}, undefined, false, function(err, existingDevice) {
|
||||||
|
/**
|
||||||
|
* Check for errors.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '7',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that nothing exists in the database.
|
||||||
|
*/
|
||||||
|
if (existingDevice || existingClient) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '318',
|
||||||
|
info: 'Email or mobile number already in use. Please log in to recover.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Email or mobile number already in use.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a completely new signup. Create client data structure.
|
||||||
|
*/
|
||||||
|
var newClient = mainDB.blankClient();
|
||||||
|
newClient.ClientName = receivedObject.ClientName;
|
||||||
|
newClient.DisplayName = '';
|
||||||
|
newClient.EMailValidationToken = utils.randomCode(utils.fullAlphaNumeric, utils.tokenLength);
|
||||||
|
newClient.EMailValidationTokenExpiry = new Date(timestamp);
|
||||||
|
newClient.EMailValidationTokenExpiry.setDate(newClient.EMailValidationTokenExpiry.getDate() + 7); // Add a week.
|
||||||
|
newClient.OperatorName = receivedObject.OperatorName;
|
||||||
|
newClient.KYC[0].ContactEmail = newClient.ClientName;
|
||||||
|
newClient.PasswordManagement[0].PasswordExpiry = new Date(timestamp);
|
||||||
|
newClient.PasswordManagement[0].PasswordExpiry.setDate(
|
||||||
|
newClient.PasswordManagement[0].PasswordExpiry.getDate() + 365); // Add a year.
|
||||||
|
newClient.PasswordManagement[0].PasswordLastReset = new Date(timestamp);
|
||||||
|
newClient.ClientPreferences[0].DefaultAccount = receivedObject.Method;
|
||||||
|
newClient.LastUpdate = new Date(timestamp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the password.
|
||||||
|
*/
|
||||||
|
auth.encryptPBKDF2(receivedObject.Password, function(err, newSalt, newHash) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '413',
|
||||||
|
info: 'Encryption error.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the new salt and hash.
|
||||||
|
*/
|
||||||
|
newClient.Password = '2::' + newHash;
|
||||||
|
newClient.ClientSalt = newSalt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the new client object.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionClient, newClient, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '6',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* E -mail successfully added - send the registration e-mail.
|
||||||
|
*/
|
||||||
|
mailer.sendWelcomeEmail(newClient, receivedObject.Mode, 'Register1.process',
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '5',
|
||||||
|
info: 'Unable to send e-mail.'
|
||||||
|
},
|
||||||
|
'ERROR', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* E -mail sent. Now set up the new device.
|
||||||
|
*/
|
||||||
|
var newDevice = mainDB.blankDevice();
|
||||||
|
if (receivedObject.DeviceHardware !== '') { // Add device hardware if sent.
|
||||||
|
newDevice.DeviceName = 'My ' + receivedObject.DeviceHardware;
|
||||||
|
}
|
||||||
|
newDevice.DeviceUuid = receivedObject.DeviceUuid;
|
||||||
|
newDevice.DeviceHardware = receivedObject.DeviceHardware;
|
||||||
|
newDevice.DeviceSoftware = receivedObject.DeviceSoftware;
|
||||||
|
newDevice.DeviceNumber = receivedObject.DeviceNumber;
|
||||||
|
newDevice.ClientID = newClient.ClientID;
|
||||||
|
newDevice.RegistrationToken = utils.randomCode(utils.numeric, utils.SMStokenLength);
|
||||||
|
newDevice.RegistrationTokenExpiry = new Date(timestamp);
|
||||||
|
newDevice.RegistrationTokenExpiry.setHours(newDevice.RegistrationTokenExpiry.getHours() +
|
||||||
|
utils.smsTokenDuration);
|
||||||
|
newDevice.SignupIP = functionInfo.remote;
|
||||||
|
newDevice.LastUpdate = new Date(timestamp);
|
||||||
|
newDevice.LastVersion = 1;
|
||||||
|
|
||||||
|
// Generate a unique device token and check it doesn't exist.
|
||||||
|
newDevice.DeviceToken = utils.randomCode(utils.fullAlphaNumeric, utils.tokenLength);
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice,
|
||||||
|
{DeviceToken: newDevice.DeviceToken}, undefined, false, function(err, tokenCheck) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '19',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device token is not unique; log this and cancel registration.
|
||||||
|
*/
|
||||||
|
if (tokenCheck !== null) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '20',
|
||||||
|
info: 'System error - token duplication.'
|
||||||
|
},
|
||||||
|
'ERROR', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All good. Add the device to the database.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionDevice, newDevice, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '8',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the registration SMS if not in test mode.
|
||||||
|
*/
|
||||||
|
sms.sendSMS(receivedObject.Mode, newDevice.DeviceNumber,
|
||||||
|
('Your Bridge verification code is ' + newDevice.RegistrationToken),
|
||||||
|
function(err, smsBalance) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '9',
|
||||||
|
info: 'SMS send failure.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' +
|
||||||
|
receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
if (receivedObject.Mode === 'Test') {
|
||||||
|
/**
|
||||||
|
* Test mode.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10011',
|
||||||
|
info: 'Register1 test successful.',
|
||||||
|
DeviceToken: newDevice.DeviceToken,
|
||||||
|
EULAVersion: config.EULAVersion
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Register 1 test successful (SMS not sent).',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' +
|
||||||
|
receivedObject.DeviceNumber + ')]'));
|
||||||
|
} else {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10000',
|
||||||
|
info: 'Register1 successful.',
|
||||||
|
DeviceToken: newDevice.DeviceToken,
|
||||||
|
EULAVersion: config.EULAVersion
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Registration SMS sent (SMS balance now ' + smsBalance + ').'),
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' +
|
||||||
|
receivedObject.DeviceNumber + ')]'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
195
node_server/ComServe/hJSON/Register2.js
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Register2 Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Verifies the mobile device by checking SMS code. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/register1/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject) {
|
||||||
|
/**
|
||||||
|
* Valid registration request. Find the device again and verify the token.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceNumber: receivedObject.DeviceNumber},
|
||||||
|
undefined, false, function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '12',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the device was found.
|
||||||
|
*/
|
||||||
|
if (!existingDevice) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '13',
|
||||||
|
info: 'Invalid device.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device cannot be found in database.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register2 only works on unauthorised devices with no flags set.
|
||||||
|
* Expected use of bitwise.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if ((existingDevice.DeviceStatus !== 0x0) &&
|
||||||
|
((existingDevice.DeviceStatus & utils.DeviceFullyRegistered) !== utils.DeviceRegister3Mask)) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '17',
|
||||||
|
info: 'This is not a new device.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device is already authorised.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//jshint +W016
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now check the device token is valid.
|
||||||
|
*/
|
||||||
|
if (receivedObject.DeviceToken !== existingDevice.DeviceToken) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '14',
|
||||||
|
info: 'Invalid device token.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now check the number of attempts that have been made on the token.
|
||||||
|
*/
|
||||||
|
if (existingDevice.RegistrationTokenAttempts >= config.maxRegTokenAttempts) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '466',
|
||||||
|
info: 'Too many registration token attempts - delete the device and start again.'
|
||||||
|
},
|
||||||
|
'WARNING', 'Too many registration token attempts.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the registration token expiry.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var expiry = existingDevice.RegistrationTokenExpiry;
|
||||||
|
if (timestamp > expiry) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '15',
|
||||||
|
info: 'Expired registration token.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the code is valid. If not, increment the failed attempts.
|
||||||
|
*/
|
||||||
|
if (receivedObject.RegistrationToken !== existingDevice.RegistrationToken) {
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceNumber: existingDevice.DeviceNumber}, {
|
||||||
|
$set: {LastUpdate: timestamp},
|
||||||
|
$inc: {
|
||||||
|
LastVersion: 1,
|
||||||
|
RegistrationTokenAttempts: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '467',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '16',
|
||||||
|
info: 'Invalid registration token.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update object and rewrite.
|
||||||
|
* Write to database. Valid bitwise operation.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
var newDeviceStatus = existingDevice.DeviceStatus | utils.DeviceRegister2Mask;
|
||||||
|
//jshint +W106
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceNumber: existingDevice.DeviceNumber}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
DeviceStatus: newDeviceStatus,
|
||||||
|
RegistrationToken: '',
|
||||||
|
RegistrationTokenExpiry: '',
|
||||||
|
RegistrationTokenAttempts: 0
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '18',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Successful new registration if equal to zero.
|
||||||
|
*/
|
||||||
|
if (existingDevice.DeviceStatus === 0x0) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10001',
|
||||||
|
info: 'Register2 successful.'
|
||||||
|
},
|
||||||
|
'INFO', 'Mobile successfully verified.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successful re-registration.
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10043',
|
||||||
|
info: 'Register2 re-registration successful.'
|
||||||
|
},
|
||||||
|
'INFO', 'Mobile successfully re-verified.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
184
node_server/ComServe/hJSON/Register4.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Register 4 Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Resends the SMS message to an existing phone. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/register4/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var sms = require(global.pathPrefix + 'sms.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject) {
|
||||||
|
// Resend the SMS message.
|
||||||
|
/**
|
||||||
|
* Valid registration request. Find the device to resend the token.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceToken: receivedObject.DeviceToken}, undefined, false,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '26',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure there is a device.
|
||||||
|
*/
|
||||||
|
if (!existingDevice) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '27',
|
||||||
|
info: 'Invalid device token.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Mobile device cannot be matched to token.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function only works on unverified devices.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if ((existingDevice.DeviceStatus !== 0x0) &&
|
||||||
|
((existingDevice.DeviceStatus & utils.DeviceFullyRegistered) !== utils.DeviceRegister3Mask)) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '28',
|
||||||
|
info: 'Device already verified.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//jshint +W016
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match the phone number.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (receivedObject.DeviceNumber !== existingDevice.DeviceNumber) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '29',
|
||||||
|
info: 'DeviceNumber mismatched.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Phone number does not match token.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match the unique device ID.
|
||||||
|
*/
|
||||||
|
if (receivedObject.DeviceUuid !== existingDevice.DeviceUuid) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '30',
|
||||||
|
info: 'DeviceUuid mismatched.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Unique device ID does not match token.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the registration token expiry.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var expiry = existingDevice.RegistrationTokenExpiry;
|
||||||
|
if (timestamp > expiry) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '31',
|
||||||
|
info: 'Invalid registration token.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Registration token is invalid.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp has at least not expired. Check that the new timestamp is at least 20 seconds.
|
||||||
|
* older than the last update.
|
||||||
|
*/
|
||||||
|
var twentySeconds = existingDevice.LastUpdate;
|
||||||
|
twentySeconds.setSeconds(twentySeconds.getSeconds() + 20);
|
||||||
|
if (twentySeconds > timestamp) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '33',
|
||||||
|
info: 'SMS 20 second timeout.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Please wait 20 seconds before requesting another SMS.',
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reasonable amount of time has been left between clicks. Re-send the registration SMS.
|
||||||
|
*/
|
||||||
|
sms.sendSMS(null, existingDevice.DeviceNumber, ('Your Bridge verification code is ' + existingDevice.RegistrationToken),
|
||||||
|
function(err, smsBalance) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '34',
|
||||||
|
info: 'SMS send failure.'
|
||||||
|
},
|
||||||
|
'ERROR',
|
||||||
|
('Cannot send SMS. ' + err),
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
var newExpiry = new Date(timestamp);
|
||||||
|
newExpiry.setHours(newExpiry.getHours() + utils.smsTokenDuration);
|
||||||
|
var newVersion = existingDevice.LastVersion + 1;
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: receivedObject.DeviceToken}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
RegistrationTokenExpiry: newExpiry,
|
||||||
|
LastVersion: newVersion
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '32',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10003',
|
||||||
|
info: 'Register4 successful.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Registration SMS re-sent. (SMS balance now ' + smsBalance + ').'),
|
||||||
|
('RI [' + receivedObject.DeviceNumber + ' (DeviceToken ' + receivedObject.DeviceToken + ')]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
200
node_server/ComServe/hJSON/Register6.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Register 6 Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Resend verification e-mail. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/register6/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject) {
|
||||||
|
/**
|
||||||
|
* Resend verification e-mail. JSON version.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Valid registration request. Find the account to resend the token.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientName: receivedObject.ClientName}, undefined, false, function(err, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '85',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the client exists.
|
||||||
|
*/
|
||||||
|
if (!existingClient) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '86',
|
||||||
|
info: 'Invalid e-mail address.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Cannot find this e-mail address in the database.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function only works if the client has never logged in. Check the flag.
|
||||||
|
* Valid bitwise comparison.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (existingClient.ClientStatus & utils.ClientEmailVerifiedMask) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '87',
|
||||||
|
info: 'Account already verified.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Account already verified (DeviceStatus bit 0x1 set).',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//jshint +W016
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the DeviceUuid.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceNumber: receivedObject.DeviceNumber}, undefined, false,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '88',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the device was found.
|
||||||
|
*/
|
||||||
|
if (!existingDevice) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '203',
|
||||||
|
info: 'Mobile phone number not available.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device does not exist.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure DeviceUuid is correct.
|
||||||
|
*/
|
||||||
|
if (receivedObject.DeviceUuid !== existingDevice.DeviceUuid) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '204',
|
||||||
|
info: 'Invalid DeviceUuid.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the registration token expiry.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
var expiry = existingClient.EMailValidationTokenExpiry;
|
||||||
|
if (timestamp > expiry) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '89',
|
||||||
|
info: 'Invalid registration token.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp has at least not expired. Check that the new timestamp is at least 20
|
||||||
|
* seconds older than the last update.
|
||||||
|
*/
|
||||||
|
var twentySeconds = existingClient.LastUpdate;
|
||||||
|
twentySeconds.setSeconds(twentySeconds.getSeconds() + 20);
|
||||||
|
if (twentySeconds > timestamp) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '90',
|
||||||
|
info: 'E-mail 20 second timeout.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Wait 20 seconds before requesting another e-mail.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update database.
|
||||||
|
*/
|
||||||
|
var newExpiry = new Date(timestamp);
|
||||||
|
newExpiry.setDate(newExpiry.getDate() + 7); // Add a week.
|
||||||
|
var newVersion = existingClient.LastVersion + 1;
|
||||||
|
mainDB.updateObject(mainDB.collectionClient, {ClientName: receivedObject.ClientName}, {
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
EMailValidationTokenExpiry: newExpiry,
|
||||||
|
LastVersion: newVersion
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '92',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resend the welcome / address confirmation e-mail.
|
||||||
|
*/
|
||||||
|
mailer.sendWelcomeEmail(existingClient, '', 'Register6.process', function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '91',
|
||||||
|
info: 'E-mail send failure.'
|
||||||
|
},
|
||||||
|
'ERROR',
|
||||||
|
'Unable to send e-mail.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10009',
|
||||||
|
info: 'Register6 successful.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Registration e-mail re-sent.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
255
node_server/ComServe/hJSON/Register7.js
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Register 7 Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Deletes an account. Add the "Mode"="ForceDelete" parameter to force deletion. HTML version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/register7/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters) {
|
||||||
|
/**
|
||||||
|
* Valid delete request. Find the e-mail address first.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientName: parameters.ClientName}, undefined, false, function(err, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '53', 'templates/undef_database_offline.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '53',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the client exists.
|
||||||
|
*/
|
||||||
|
if (!existingClient) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '54', 'templates/54_email_not_found.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '54',
|
||||||
|
ClientName: parameters.ClientName,
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'E-mail does not exist.',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client does exist - pull the device.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceNumber: parameters.DeviceNumber}, undefined, false,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '55', 'templates/undef_database_offline.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '55',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check we got a device match.
|
||||||
|
*/
|
||||||
|
if (!existingDevice) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '56', 'templates/56_mobile_number_not_found.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '56',
|
||||||
|
DeviceNumber: parameters.DeviceNumber,
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device does not exist.',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OK, both exist. Run checks to ensure it can be deleted.
|
||||||
|
*/
|
||||||
|
if (existingClient.ClientID !== existingDevice.ClientID) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '57', 'templates/57_association_error.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '57',
|
||||||
|
ClientName: parameters.ClientName,
|
||||||
|
DeviceNumber: parameters.DeviceNumber,
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device is not registered to this e-mail address.',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check registration status. If FirstLogin !== 1, disallow the deletion.
|
||||||
|
*/
|
||||||
|
if (existingClient.FirstLogin !== 1) {
|
||||||
|
/**
|
||||||
|
* There is one exception - if this is the Dev server and ForceDelete is sent.
|
||||||
|
*/
|
||||||
|
if ((('Mode' in parameters) && (parameters.Mode === 'ForceDelete')) && config.isDevEnv) {
|
||||||
|
/**
|
||||||
|
* Deletion will be forced as this is the Dev server.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Forced deletion initialised (Mode = ForceDelete).',
|
||||||
|
'Register7.process',
|
||||||
|
'',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
} else {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '58', 'templates/58_fully_registered.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '58',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'This is a fully registered account.',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the existing client before archiving it
|
||||||
|
* - Move _id to OldClientID: Note the clash with the
|
||||||
|
* client identifier ClientID which must be retained.
|
||||||
|
* - Remove the existing Password and ClientSalt.
|
||||||
|
* - Update the LastUpdate time.
|
||||||
|
*/
|
||||||
|
var clientId = existingClient._id;
|
||||||
|
existingClient.OldClientID = clientId.toString();
|
||||||
|
delete existingClient._id;
|
||||||
|
existingClient.Password = '';
|
||||||
|
existingClient.ClientSalt = '';
|
||||||
|
existingClient.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back up Client to the Archive.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionClientArchive, existingClient, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '146', 'templates/undef_database_offline.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '146',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now remove the Client.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionClient, {_id: clientId}, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '59', 'templates/undef_database_offline.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '59',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report that the Client is out of database.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Client has been successfully removed.',
|
||||||
|
'Register7.process',
|
||||||
|
'10005',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back up existing Device.
|
||||||
|
*/
|
||||||
|
var deviceId = existingDevice._id;
|
||||||
|
existingDevice.DeviceIndex = existingDevice._id.toString();
|
||||||
|
delete existingDevice._id;
|
||||||
|
existingDevice.DeviceAuthorisation = '';
|
||||||
|
existingDevice.DeviceSalt = '';
|
||||||
|
existingDevice.PendingHMAC = '';
|
||||||
|
existingDevice.CurrentHMAC = '';
|
||||||
|
existingDevice.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back up existing Device.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionDeviceArchive, existingDevice, undefined, false, function(err) {
|
||||||
|
// Check for errors.
|
||||||
|
if (err) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '145', 'templates/undef_database_offline.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '145',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device added to archive. Delete from active devices.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionDevice, {_id: deviceId}, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '60', 'templates/undef_database_offline.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
errornumber: '60',
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device removed from database.
|
||||||
|
*/
|
||||||
|
auth.respondHTML(res, 200, functionInfo, '10005', 'templates/10005_reg_deleted.pug', {
|
||||||
|
pretty: true,
|
||||||
|
title: 'Comcarde Bridge',
|
||||||
|
ClientName: parameters.ClientName,
|
||||||
|
DeviceNumber: parameters.DeviceNumber,
|
||||||
|
ipInfo: functionInfo.remote
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Device has been successfully removed.',
|
||||||
|
('RI [' + parameters.ClientName + ' (' + parameters.DeviceNumber + ')]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
227
node_server/ComServe/hJSON/Register8.js
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Register 8 Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Deletes an account. Add the "Mode"="ForceDelete" parameter to force deletion. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/register8/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject) {
|
||||||
|
/**
|
||||||
|
* Valid registration request. Find the e-mail address first.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionClient, {ClientName: receivedObject.ClientName}, undefined, false,
|
||||||
|
function(err, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '61',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid delete request. Find the e-mail address first.
|
||||||
|
*/
|
||||||
|
if (!existingClient) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '62',
|
||||||
|
info: 'E-mail address does not exist.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OK, so the client does exist. Check for the device.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice, {DeviceNumber: receivedObject.DeviceNumber}, undefined, false,
|
||||||
|
function(err, existingDevice) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '63',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the device second.
|
||||||
|
*/
|
||||||
|
if (!existingDevice) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '64',
|
||||||
|
info: 'Mobile phone number not in use.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Device does not exist.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OK, both exist. Run checks to ensure they can be deleted.
|
||||||
|
* Firstly, check that they are not wrongly linked.
|
||||||
|
*/
|
||||||
|
if (existingClient.ClientID !== existingDevice.ClientID) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '65',
|
||||||
|
info: 'Device not registered to this e-mail address.'
|
||||||
|
},
|
||||||
|
'WARNING', null,
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check registration status. If FirstLogin !== 1, disallow the deletion.
|
||||||
|
*/
|
||||||
|
if (existingClient.FirstLogin !== 1) {
|
||||||
|
/**
|
||||||
|
* There is one exception - if this is the Dev server and ForceDelete is sent.
|
||||||
|
*/
|
||||||
|
if ((('Mode' in receivedObject) && (receivedObject.Mode === 'ForceDelete')) && config.isDevEnv) {
|
||||||
|
/**
|
||||||
|
* Deletion will be forced as this is the Dev server.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Forced deletion initialised (Mode = ForceDelete).',
|
||||||
|
'Register8.process',
|
||||||
|
'',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
} else {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '66',
|
||||||
|
info: 'Account fully registered.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'This is a fully registered account.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the existing client before archiving it
|
||||||
|
* - Move _id to ClientID
|
||||||
|
* - Remove the existing Password and ClientSalt.
|
||||||
|
* - Update the LastUpdate time
|
||||||
|
*/
|
||||||
|
var clientId = existingClient._id;
|
||||||
|
existingClient.OldClientID = clientId.toString();
|
||||||
|
delete existingClient._id;
|
||||||
|
existingClient.Password = '';
|
||||||
|
existingClient.ClientSalt = '';
|
||||||
|
existingClient.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back up Client Archive.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionClientArchive, existingClient, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '147',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The account can be safely deleted.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionClient, {_id: clientId}, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '67',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client is out of database.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Client has been successfully removed.',
|
||||||
|
'Register8.process',
|
||||||
|
'10006',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'),
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back up existing Device.
|
||||||
|
*/
|
||||||
|
var deviceId = existingDevice._id;
|
||||||
|
existingDevice.DeviceIndex = existingDevice._id.toString();
|
||||||
|
delete existingDevice._id;
|
||||||
|
existingDevice.DeviceAuthorisation = '';
|
||||||
|
existingDevice.DeviceSalt = '';
|
||||||
|
existingDevice.PendingHMAC = '';
|
||||||
|
existingDevice.CurrentHMAC = '';
|
||||||
|
existingDevice.LastUpdate = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Archive it.
|
||||||
|
*/
|
||||||
|
mainDB.addObject(mainDB.collectionDeviceArchive, existingDevice, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '148',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove device.
|
||||||
|
*/
|
||||||
|
mainDB.removeObject(mainDB.collectionDevice, {_id: deviceId}, undefined, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '68',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device is out of database
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '10006',
|
||||||
|
info: 'Client and Device removed.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Device has been successfully removed.',
|
||||||
|
('RI [' + receivedObject.ClientName + ' (' + receivedObject.DeviceNumber + ')]'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
184
node_server/ComServe/hJSON/RejectInvoice.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Rejects an invoice (with optional comment)
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/merchant_commands/reject_invoice/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var templates = require(global.pathPrefix + '../utils/templates.js');
|
||||||
|
var Q = require('q');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mailer = require(global.pathPrefix + 'mailer.js');
|
||||||
|
var formattingUtils = require(global.pathPrefix + '../utils/formatting.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rejects an invoice that the client doesn't believe is correct. An optional
|
||||||
|
* comment can be provided.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in invoice body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the query for the invoice we are rejecting. The limits are:
|
||||||
|
* - ClientID of the invoice must match the current client
|
||||||
|
* - _id must match the given InvoiceID
|
||||||
|
* - Invoice must be in Pending state
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
_id: mongodb.ObjectID(receivedObject.InvoiceID),
|
||||||
|
CustomerClientID: existingClient.ClientID,
|
||||||
|
TransactionStatus: utils.TransactionStatus.PENDING_INVOICE
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the projection
|
||||||
|
*/
|
||||||
|
var projection = {
|
||||||
|
_id: 1,
|
||||||
|
/* Values required for formatting the notification email */
|
||||||
|
MerchantClientID: 1,
|
||||||
|
CustomerDisplayName: 1,
|
||||||
|
MerchantInvoiceNumber: 1,
|
||||||
|
CustomerComment: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
projection: projection,
|
||||||
|
upsert: false,
|
||||||
|
returnOriginal: false, // Return the updated document
|
||||||
|
comment: 'RejectInvoice' // For profiler logs use
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update values
|
||||||
|
*/
|
||||||
|
var update = {
|
||||||
|
$set: {
|
||||||
|
TransactionStatus: utils.TransactionStatus.REJECTED_INVOICE
|
||||||
|
},
|
||||||
|
$inc: {
|
||||||
|
LastVersion: 1
|
||||||
|
},
|
||||||
|
$currentDate: {
|
||||||
|
LastUpdate: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (receivedObject.Comment) {
|
||||||
|
update.$set.CustomerComment = receivedObject.Comment;
|
||||||
|
} else {
|
||||||
|
update.$set.CustomerComment = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the invoice
|
||||||
|
*/
|
||||||
|
mainDB.collectionTransaction.findOneAndUpdate(
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
options,
|
||||||
|
function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '517',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Couldn't find anything to update
|
||||||
|
*/
|
||||||
|
if (!response.value) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '518',
|
||||||
|
info: 'Invalid InvoiceID or not in Pending status.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the merchant that the customer has queried the invoice.
|
||||||
|
* This doesn't affect the success of querying the invoice.
|
||||||
|
*/
|
||||||
|
notifyRejectedInvoice(
|
||||||
|
response.value.MerchantClientID,
|
||||||
|
response.value
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the success of querying the invoice
|
||||||
|
*/
|
||||||
|
auth.respond(
|
||||||
|
res,
|
||||||
|
200,
|
||||||
|
existingDevice,
|
||||||
|
hmacData,
|
||||||
|
functionInfo, {
|
||||||
|
code: '10077',
|
||||||
|
info: 'Invoice rejected.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
'Invoice rejected (InvoiceID ' + receivedObject.InvoiceID + ').'
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the merchant that an existing invoice has been queried by the customer.
|
||||||
|
*
|
||||||
|
* @param {string} merchantID - the merchant's ID (to find their email address)
|
||||||
|
* @param {object} invoice - the invoice (for adding info to the email)
|
||||||
|
*
|
||||||
|
* @returns {Promise} - a promise for the result of notifying the customer
|
||||||
|
*/
|
||||||
|
function notifyRejectedInvoice(merchantID, invoice) {
|
||||||
|
var reviewUrl = formattingUtils.formatPortalUrl(
|
||||||
|
'business/invoices/' + invoice._id.toString() + '/update'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the html for the email
|
||||||
|
*/
|
||||||
|
var htmlEmail = templates.render('invoice-queried', {
|
||||||
|
customer: invoice.CustomerDisplayName,
|
||||||
|
number: invoice.MerchantInvoiceNumber.InvoiceNumber,
|
||||||
|
comment: invoice.CustomerComment,
|
||||||
|
reviewUrl: reviewUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
return Q.nfcall(
|
||||||
|
mailer.sendEmailByID,
|
||||||
|
'', // Mode ('Test' to just log, anything else to send)
|
||||||
|
merchantID, // Destination
|
||||||
|
'Queried Invoice', // Subject
|
||||||
|
htmlEmail,
|
||||||
|
'notifyRejectedInvoice'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
140
node_server/ComServe/hJSON/ReportImage.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Report Image Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Marks an image as offensive in the database.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/image_commands/reportimage/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether client is allowed to report images.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (existingClient.ClientStatus & utils.ClientCantReport) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '224',
|
||||||
|
info: 'Reporting disabled.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Client not allowed to flag images.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//jshint +W016
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check this is not one of the Client's own images.
|
||||||
|
*/
|
||||||
|
if ((receivedObject.ImageRef === config.defaultSelfie) ||
|
||||||
|
(receivedObject.ImageRef === config.defaultCompanyLogo0) ||
|
||||||
|
(receivedObject.ImageRef === existingClient.Selfie) ||
|
||||||
|
(receivedObject.ImageRef === existingClient.Merchant[0].CompanyLogo)) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '326',
|
||||||
|
info: 'Client cannot report default or their own images.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the image file to return.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionImages,
|
||||||
|
{_id: mongodb.ObjectID(receivedObject.ImageRef)},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, existingImage) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '327',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the image exists.
|
||||||
|
*/
|
||||||
|
if (!existingImage) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '328',
|
||||||
|
info: 'Invalid ImageRef.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Client reported an invalid ImageRef.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image exists. Report again requires a counter increment.
|
||||||
|
*/
|
||||||
|
var newLastUpdate = new Date();
|
||||||
|
var newImageReported = existingImage.ImageReported;
|
||||||
|
if (newImageReported < 9999) {
|
||||||
|
newImageReported = newImageReported + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the database.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionImages, {_id: mongodb.ObjectID(receivedObject.ImageRef)}, {
|
||||||
|
$set: {
|
||||||
|
ImageReported: newImageReported,
|
||||||
|
LastUpdate: newLastUpdate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '288',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the user that the image has been marked as reported.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10034',
|
||||||
|
info: 'Image reported.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('Image reported (ID ' + receivedObject.ImageRef + ').'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
140
node_server/ComServe/hJSON/ResumeDevice.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Resume Device Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Resumes a particular device if it belongs to the current client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/resumedevice/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Local variables
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the current password.
|
||||||
|
*/
|
||||||
|
auth.checkClientPassword(receivedObject.Password, existingClient, timestamp, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the device.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectId(receivedObject.DeviceIndex),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, device) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '433',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No hits from database.
|
||||||
|
*/
|
||||||
|
if (device === null) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '434',
|
||||||
|
info: 'Invalid device or device does not belong to client.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device has not been suspended.
|
||||||
|
* Valid bitwise operation.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (!(device.DeviceStatus & utils.DeviceSuspendedMask)) {
|
||||||
|
//jshint +W016
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '435',
|
||||||
|
info: 'Device has not been suspended.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device can be resumed.
|
||||||
|
* Correct use of bitwise manipulation.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {_id: mongodb.ObjectId(receivedObject.DeviceIndex)}, {
|
||||||
|
$bit: {
|
||||||
|
DeviceStatus: {and: ~utils.DeviceSuspendedMask}
|
||||||
|
},
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
//jshint +W016
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '436',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10063',
|
||||||
|
info: 'Device Resumed.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
98
node_server/ComServe/hJSON/RotateHMAC.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js RotateHMAC Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Confirms the new HMAC has been received, accepted and stored by the device. JSON version.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/login_auth/rotatehmac/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is no pending HMAC, do nothing.
|
||||||
|
*/
|
||||||
|
if (existingDevice.PendingHMAC === '') {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '442',
|
||||||
|
info: 'No pending HMAC.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the call is signed with the Pending HMAC.
|
||||||
|
*/
|
||||||
|
auth.checkHMAC(existingDevice, hmacData, 'RotateHMAC.process', function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: err.code.toString(),
|
||||||
|
info: err.message
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate the HMAC. Clear HMAC attempts as a bad HMAC may have corrupted the CurrentHMAC.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: existingDevice.DeviceToken},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
PendingHMAC: '',
|
||||||
|
CurrentHMAC: existingDevice.PendingHMAC,
|
||||||
|
HMACAttempts: 0,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '443',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HMAC successfully updated.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10065',
|
||||||
|
info: 'HMAC rotation successful.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
148
node_server/ComServe/hJSON/SendReport.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Send Report Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Returns a list of transactions on an account for the referenced user.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/misc_commands/sendreport/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var moment = require('moment');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module variables.
|
||||||
|
*/
|
||||||
|
var bankFees = 29; // RBS charges 29p.
|
||||||
|
var incomingIP = '62.232.80.210';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters) {
|
||||||
|
/**
|
||||||
|
* Operations are only allowed in certain situations or from certain locations.
|
||||||
|
*/
|
||||||
|
if ((functionInfo.remote !== incomingIP) || (!config.isDevEnv)) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '317',
|
||||||
|
info: 'Invalid IP or not Dev server.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the relevant transactions.
|
||||||
|
* Note that the cyclomatic complexity is known to be high.
|
||||||
|
*/
|
||||||
|
//jshint -W074
|
||||||
|
mainDB.collectionTransaction.find(
|
||||||
|
{
|
||||||
|
MerchantClientID: parameters.MerchantClientID,
|
||||||
|
SaleTime: {'$gte': new Date(parameters.DateGTE), '$lt': new Date(parameters.DateLT)}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
CustomerDisplayName: 1,
|
||||||
|
SaleTime: 1,
|
||||||
|
RequestAmount: 1,
|
||||||
|
TipAmount: 1,
|
||||||
|
SaleReference: 1,
|
||||||
|
SaleAuthCode: 1,
|
||||||
|
TransactionStatus: 1
|
||||||
|
}
|
||||||
|
).sort({SaleTime: -1}).toArray(function(err, items) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, null, null, functionInfo, {
|
||||||
|
code: '316',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond with a list of items.
|
||||||
|
*/
|
||||||
|
if (items) {
|
||||||
|
/**
|
||||||
|
* Filter the account information.
|
||||||
|
*/
|
||||||
|
var total = 0;
|
||||||
|
var tipTotal = 0;
|
||||||
|
var counter = 0;
|
||||||
|
var timestamp = new Date();
|
||||||
|
var csvFile = 'BRIDGE activity report for ' + parameters.MerchantClientName + utils.CarriageReturn;
|
||||||
|
csvFile += 'Generated:' + timestamp + utils.CarriageReturn;
|
||||||
|
csvFile += 'Period: GTE ' + parameters.DateGTE + ', LT ' + parameters.DateLT +
|
||||||
|
utils.CarriageReturn + utils.CarriageReturn;
|
||||||
|
csvFile += 'Date\t\tTime\t\tCustomer\t\tAmount\tTip\tSale Reference\tAuth\tTransactionID' + utils.CarriageReturn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go through each item and return a subset of information.
|
||||||
|
*/
|
||||||
|
while (counter < items.length) {
|
||||||
|
/**
|
||||||
|
* Select card information.
|
||||||
|
*/
|
||||||
|
if ((items[counter].TransactionStatus === 3) || (items[counter].TransactionStatus === 4)) {
|
||||||
|
csvFile += moment(items[counter].SaleTime).format('DD-MM-YYYY') + '\t';
|
||||||
|
csvFile += moment(items[counter].SaleTime).format('HH:mm:ss') + '\t';
|
||||||
|
csvFile += items[counter].CustomerDisplayName + '\t\t';
|
||||||
|
if (items[counter].TransactionStatus === 3) {
|
||||||
|
csvFile += (items[counter].RequestAmount / 100).toFixed(2) + '\t';
|
||||||
|
} else {
|
||||||
|
csvFile += (items[counter].RequestAmount / 100).toFixed(2) + 'R\t';
|
||||||
|
}
|
||||||
|
if (items[counter].TransactionStatus === 3) {
|
||||||
|
csvFile += (items[counter].TipAmount / 100).toFixed(2) + '\t';
|
||||||
|
} else {
|
||||||
|
csvFile += (items[counter].TipAmount / 100).toFixed(2) + 'R\t';
|
||||||
|
}
|
||||||
|
csvFile += items[counter].SaleReference + '\t';
|
||||||
|
csvFile += items[counter].SaleAuthCode + '\t';
|
||||||
|
csvFile += items[counter]._id + utils.CarriageReturn;
|
||||||
|
if (items[counter].TransactionStatus === 3) {
|
||||||
|
total += items[counter].RequestAmount;
|
||||||
|
tipTotal += items[counter].TipAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
counter++; // Always increment the counter.
|
||||||
|
}
|
||||||
|
csvFile += utils.CarriageReturn + '-----------------------' + utils.CarriageReturn + 'Total Amount:\t' +
|
||||||
|
(total / 100).toFixed(2) + utils.CarriageReturn;
|
||||||
|
csvFile += 'Total Tip:\t' + (tipTotal / 100).toFixed(2) + utils.CarriageReturn;
|
||||||
|
csvFile += 'Bank Fees:\t' + (bankFees / 100).toFixed(2) + utils.CarriageReturn;
|
||||||
|
csvFile += '-----------------------' + utils.CarriageReturn + 'Total (GBP):\t' +
|
||||||
|
((total + tipTotal + bankFees) / 100).toFixed(2) + utils.CarriageReturn + '-----------------------';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the information.
|
||||||
|
*/
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||||
|
res.end(csvFile);
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Report sent.',
|
||||||
|
'SendReport.process',
|
||||||
|
'',
|
||||||
|
'UU',
|
||||||
|
(functionInfo.remote + ' (' + functionInfo.port + ')'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//jshint +W074
|
||||||
|
};
|
176
node_server/ComServe/hJSON/SetAccountAddress.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Set Account Address Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Allows the user to change the address associated with an account.
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/setaccountaddress/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the address.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAddresses,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectID(receivedObject.AddressID),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 1
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
function(err, existingAddress) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '392',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that an address was found.
|
||||||
|
*/
|
||||||
|
if (!existingAddress) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '393',
|
||||||
|
info: 'Cannot find address.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the account.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAccount,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectID(receivedObject.AccountID),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
AccountStatus: 1,
|
||||||
|
BillingAddress: 1
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
function(err, existingAccount) {
|
||||||
|
/**
|
||||||
|
* Check for errors.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '394',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that an account was found.
|
||||||
|
*/
|
||||||
|
if (!existingAccount) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '395',
|
||||||
|
info: 'Cannot find account.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for deleted accounts.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (existingAccount.AccountStatus & utils.AccountDeleted) {
|
||||||
|
//jshint +W016
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '396',
|
||||||
|
info: 'Cannot change a deleted account.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Different response if there is no change.
|
||||||
|
*/
|
||||||
|
if (existingAccount.BillingAddress === receivedObject.AddressID) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10056',
|
||||||
|
info: 'BillingAddress already set to this AddressID.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the account with the new Address.
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
mainDB.updateObject(mainDB.collectionAccount,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectID(receivedObject.AccountID),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
BillingAddress: receivedObject.AddressID,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '397',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account address successfully set.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10055',
|
||||||
|
info: 'Account address set.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
106
node_server/ComServe/hJSON/SetClientDetails.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Set Client Details Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Sets the client's KYC details in the Client record.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/setclientdetails/}
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var httpStatus = require('http-status-codes');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var references = require(global.pathPrefix + '../utils/references.js');
|
||||||
|
var responsesUtils = require(global.pathPrefix + '../utils/responses.js');
|
||||||
|
var diligence = require(global.pathPrefix + '../utils/diligence/diligence.js');
|
||||||
|
var clientUtils = require(global.pathPrefix + '../utils/client/client.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Get the current user's email from the session
|
||||||
|
//
|
||||||
|
var setP = clientUtils.setKyc(existingClient, receivedObject);
|
||||||
|
|
||||||
|
setP.then((result) => {
|
||||||
|
//
|
||||||
|
// We may have warnings to respond with
|
||||||
|
//
|
||||||
|
const responses = [
|
||||||
|
[
|
||||||
|
clientUtils.SETKYC_RESPONSES.OK,
|
||||||
|
httpStatus.OK, 10059, 'Client details set.'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
clientUtils.SETKYC_RESPONSES.WARNING_REFER,
|
||||||
|
httpStatus.OK, 10079, 'Additional information required.'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
clientUtils.SETKYC_RESPONSES.WARNING_INTERNAL_CHECKS,
|
||||||
|
httpStatus.OK, 10080, 'Additional internal checks required.'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const responseHandler = new responsesUtils.ErrorResponses(responses);
|
||||||
|
responseHandler.respondAuth(
|
||||||
|
res, result, existingDevice, hmacData, functionInfo, 'INFO'
|
||||||
|
);
|
||||||
|
}).catch((error) => {
|
||||||
|
const responses = [
|
||||||
|
[
|
||||||
|
'MongoError',
|
||||||
|
httpStatus.OK, 423, 'Database Offline', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
references.ERRORS.INVALID_ADDRESS,
|
||||||
|
httpStatus.OK, 532, 'Invalid Address', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
diligence.ERRORS.VERIFICATION_FAILED,
|
||||||
|
httpStatus.OK, 533, 'Unable to verify id', true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
clientUtils.SETKYC_ERRORS.DOB_MISMATCH,
|
||||||
|
httpStatus.OK, 426, 'Date of birth mismatch'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
clientUtils.SETKYC_ERRORS.UPDATE_FAILED,
|
||||||
|
httpStatus.OK, 534, 'Client not found during update'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
clientUtils.SETKYC_ERRORS.INVALID_PARAMETERS,
|
||||||
|
httpStatus.OK, 535, 'Invalid paramters'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const responseHandler = new responsesUtils.ErrorResponses(responses);
|
||||||
|
responseHandler.respondAuth(
|
||||||
|
res, error, existingDevice, hmacData, functionInfo, 'INFO'
|
||||||
|
);
|
||||||
|
}).done();
|
||||||
|
});
|
||||||
|
};
|
165
node_server/ComServe/hJSON/SetDefaultAccount.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Set Default Account Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Sets the default account for the user.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/account_commands/setdefaultaccount/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Local variables
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if this is a clear default account.
|
||||||
|
*/
|
||||||
|
if (receivedObject.AccountID === '') {
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: receivedObject.DeviceToken}, {
|
||||||
|
$set: {
|
||||||
|
DefaultAccount: '',
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '314',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success! There are different return codes for set or clear.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10046',
|
||||||
|
info: 'Default account cleared.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set request. Get the account from the database.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionAccount,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectID(receivedObject.AccountID),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, existingAccount) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '301',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No hits from database.
|
||||||
|
*/
|
||||||
|
if (existingAccount === null) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '302',
|
||||||
|
info: 'No account match.'
|
||||||
|
},
|
||||||
|
'WARNING',
|
||||||
|
'Invalid AccountID or Account does not belong to client.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to ensure that the account has not already been deleted.
|
||||||
|
* Valid bitwise comparison.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (existingAccount.AccountStatus & utils.AccountDeleted) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '304',
|
||||||
|
info: 'Account has been deleted.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingAccount.AccountStatus & utils.AccountApiCreated) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '556',
|
||||||
|
info: 'Unsupported account type.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//jshint +W016
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the default account.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {DeviceToken: receivedObject.DeviceToken}, {
|
||||||
|
$set: {
|
||||||
|
DefaultAccount: receivedObject.AccountID,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '305',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success!
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10045',
|
||||||
|
info: 'Account successfully set as default.'
|
||||||
|
},
|
||||||
|
'INFO',
|
||||||
|
('AccountID ' + receivedObject.AccountID + ' set as default.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
92
node_server/ComServe/hJSON/SetDeviceName.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Set Device Name Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Sets the name of a particular device if it belongs to the current client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/setdevicename/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Local variables
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the device.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectId(receivedObject.DeviceIndex),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
DeviceName: receivedObject.DeviceName,
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err, result) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '439',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No hits from database if 0 or less.
|
||||||
|
*/
|
||||||
|
if (result.result.n <= 0) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '440',
|
||||||
|
info: 'Invalid device or device does not belong to client.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10064',
|
||||||
|
info: 'Device name set.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
136
node_server/ComServe/hJSON/SuspendDevice.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Suspend Device Handler for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Suspends a particular device if it belongs to the current client.
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/server_interface/registration_commands/suspenddevice/}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var auth = require(global.pathPrefix + 'auth.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Server elements. Processes an HTTP request, be it JSON or HTTP.
|
||||||
|
* For a full description of functionality, please see the link in fileOverview.
|
||||||
|
*
|
||||||
|
* @type {function} process
|
||||||
|
* @param {!object} res - Response object for returning information.
|
||||||
|
* @param {!object} functionInfo - detail on the calling function {!name, !remote, !port}
|
||||||
|
* @param {?object} parameters - Input parameters posted on link.
|
||||||
|
* @param {?object} receivedObject - Input parameters in message body.
|
||||||
|
* @param {?object} hmacData - HMAC information from incoming packet.
|
||||||
|
*/
|
||||||
|
exports.process = function(res, functionInfo, parameters, receivedObject, hmacData) {
|
||||||
|
/**
|
||||||
|
* Local variables
|
||||||
|
*/
|
||||||
|
var timestamp = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the session. This function responds directly if there is a problem.
|
||||||
|
*/
|
||||||
|
auth.validSession(res, receivedObject.DeviceToken, receivedObject.SessionToken, functionInfo, hmacData,
|
||||||
|
function(err, existingDevice, existingClient) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the device.
|
||||||
|
*/
|
||||||
|
mainDB.findOneObject(mainDB.collectionDevice,
|
||||||
|
{
|
||||||
|
_id: mongodb.ObjectId(receivedObject.DeviceIndex),
|
||||||
|
ClientID: existingClient.ClientID
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
function(err, device) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '428',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No hits from database.
|
||||||
|
*/
|
||||||
|
if (device === null) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '429',
|
||||||
|
info: 'Invalid device or device does not belong to client.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cannot suspend the device that is being used.
|
||||||
|
*/
|
||||||
|
if (device.DeviceNumber === existingDevice.DeviceNumber) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '430',
|
||||||
|
info: 'Cannot suspend the device currently in use.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device will not be suspended as it is already suspended.
|
||||||
|
* Valid bitwise operation.
|
||||||
|
*/
|
||||||
|
//jshint -W016
|
||||||
|
if (device.DeviceStatus & utils.DeviceSuspendedMask) {
|
||||||
|
//jshint +W016
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '437',
|
||||||
|
info: 'Device has already been suspended.'
|
||||||
|
},
|
||||||
|
'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device can be suspended.
|
||||||
|
*/
|
||||||
|
mainDB.updateObject(mainDB.collectionDevice, {_id: mongodb.ObjectId(receivedObject.DeviceIndex)}, {
|
||||||
|
$bit: {
|
||||||
|
DeviceStatus: {or: utils.DeviceSuspendedMask}
|
||||||
|
},
|
||||||
|
$set: {
|
||||||
|
LastUpdate: timestamp
|
||||||
|
},
|
||||||
|
$inc: {LastVersion: 1}
|
||||||
|
},
|
||||||
|
{upsert: false}, false, function(err) {
|
||||||
|
if (err) {
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '431',
|
||||||
|
info: 'Database offline.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
auth.respond(res, 200, existingDevice, hmacData, functionInfo, {
|
||||||
|
code: '10062',
|
||||||
|
info: 'Device suspended.'
|
||||||
|
},
|
||||||
|
'INFO');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
322
node_server/ComServe/hJSON/specs/ElevateSession.spec.js
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
/**
|
||||||
|
* Unit testing file for ElevateSession command
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
/* eslint max-nested-callbacks: ["error", 5] */
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const testGlobals = require('../../../tools/test/testGlobals.js');
|
||||||
|
const chai = require('chai');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const sinonChai = require('sinon-chai');
|
||||||
|
const chaiAsPromised = require('chai-as-promised');
|
||||||
|
const rewire = require('rewire');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `rewire` instead of require so that we can access private functions for test
|
||||||
|
*/
|
||||||
|
const elevateSession = rewire('../ElevateSession.js');
|
||||||
|
const authStub = elevateSession.__get__('authP');
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
chai.use(sinonChai);
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a sample Client and Device object to return
|
||||||
|
*/
|
||||||
|
const DEVICE_TOKEN = 'abc123';
|
||||||
|
const SESSION_TOKEN = 'def456';
|
||||||
|
const CLIENT_EMAIL = 'a@example.com';
|
||||||
|
const PASSWORD = '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8'; // "password"
|
||||||
|
|
||||||
|
const FAKE_CLIENT = {
|
||||||
|
ClientName: CLIENT_EMAIL
|
||||||
|
};
|
||||||
|
const FAKE_DEVICE = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values for testing failures
|
||||||
|
*/
|
||||||
|
const NOT_DEVICE_TOKEN = 'ghi789';
|
||||||
|
const NOT_SESSION_TOKEN = 'jkl012';
|
||||||
|
const NOT_CLIENT_EMAIL = 'not-a@example.com';
|
||||||
|
const NOT_PASSWORD = '05f721989a4f70756a3b8387767affd11c776a0c863f1a22410855e606753321'; // "notpassword"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define some fake parameters
|
||||||
|
*/
|
||||||
|
const fakeFunctionInfo = {};
|
||||||
|
const fakeParameters = {};
|
||||||
|
const fakeHmacData = [];
|
||||||
|
|
||||||
|
const res = sinon.spy();
|
||||||
|
|
||||||
|
describe('ElevateSession', () => {
|
||||||
|
describe('with valid parameters', () => {
|
||||||
|
let callP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(authStub, 'validSession').resolves([FAKE_DEVICE, FAKE_CLIENT]);
|
||||||
|
sinon.stub(authStub, 'checkClientPassword').resolves('');
|
||||||
|
sinon.stub(authStub, 'respond').returns();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
DeviceToken: DEVICE_TOKEN,
|
||||||
|
SessionToken: SESSION_TOKEN,
|
||||||
|
ClientName: CLIENT_EMAIL,
|
||||||
|
Password: PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
callP = elevateSession.process(res, fakeFunctionInfo, fakeParameters, testData, fakeHmacData);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
authStub.validSession.restore();
|
||||||
|
authStub.checkClientPassword.restore();
|
||||||
|
authStub.respond.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs', () => {
|
||||||
|
return expect(callP).to.eventually.be.fulfilled;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates the session', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.validSession).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(res, DEVICE_TOKEN, SESSION_TOKEN)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the client password', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.checkClientPassword).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(PASSWORD, FAKE_CLIENT)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.respond).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWithMatch(
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match({
|
||||||
|
code: '10079',
|
||||||
|
info: 'Session Elevated.'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('without valid session', () => {
|
||||||
|
let callP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(authStub, 'validSession').rejects();
|
||||||
|
sinon.stub(authStub, 'checkClientPassword').resolves('');
|
||||||
|
sinon.stub(authStub, 'respond').returns();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
DeviceToken: NOT_DEVICE_TOKEN,
|
||||||
|
SessionToken: NOT_SESSION_TOKEN,
|
||||||
|
ClientName: CLIENT_EMAIL,
|
||||||
|
Password: PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
callP = elevateSession.process(res, fakeFunctionInfo, fakeParameters, testData, fakeHmacData);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
authStub.validSession.restore();
|
||||||
|
authStub.checkClientPassword.restore();
|
||||||
|
authStub.respond.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs', () => {
|
||||||
|
return expect(callP).to.eventually.be.fulfilled;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates the session', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.validSession).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(res, NOT_DEVICE_TOKEN, NOT_SESSION_TOKEN)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails before checking password', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.checkClientPassword).to.not.have.been.called
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT respond (validSession deals with the response)', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.respond).to.have.not.been.called
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with wrong email address', () => {
|
||||||
|
let callP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(authStub, 'validSession').resolves([FAKE_DEVICE, FAKE_CLIENT]);
|
||||||
|
sinon.stub(authStub, 'checkClientPassword').resolves('');
|
||||||
|
sinon.stub(authStub, 'respond').returns();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
DeviceToken: DEVICE_TOKEN,
|
||||||
|
SessionToken: SESSION_TOKEN,
|
||||||
|
ClientName: NOT_CLIENT_EMAIL,
|
||||||
|
Password: PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
callP = elevateSession.process(res, fakeFunctionInfo, fakeParameters, testData, fakeHmacData);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
authStub.validSession.restore();
|
||||||
|
authStub.checkClientPassword.restore();
|
||||||
|
authStub.respond.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs', () => {
|
||||||
|
return expect(callP).to.eventually.be.fulfilled;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates the session', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.validSession).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(res, DEVICE_TOKEN, SESSION_TOKEN)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails before checking password', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.checkClientPassword).to.not.have.been.called
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with correct error', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.respond).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWithMatch(
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match({
|
||||||
|
code: '559',
|
||||||
|
info: 'Invalid ClientName.'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with wrong email password', () => {
|
||||||
|
let callP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(authStub, 'validSession').resolves([FAKE_DEVICE, FAKE_CLIENT]);
|
||||||
|
sinon.stub(authStub, 'checkClientPassword').rejects({
|
||||||
|
code: 123,
|
||||||
|
message: 'One of many password errors'
|
||||||
|
});
|
||||||
|
sinon.stub(authStub, 'respond').returns();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
DeviceToken: DEVICE_TOKEN,
|
||||||
|
SessionToken: SESSION_TOKEN,
|
||||||
|
ClientName: CLIENT_EMAIL,
|
||||||
|
Password: NOT_PASSWORD
|
||||||
|
};
|
||||||
|
|
||||||
|
callP = elevateSession.process(res, fakeFunctionInfo, fakeParameters, testData, fakeHmacData);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
authStub.validSession.restore();
|
||||||
|
authStub.checkClientPassword.restore();
|
||||||
|
authStub.respond.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs', () => {
|
||||||
|
return expect(callP).to.eventually.be.fulfilled;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates the session', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.validSession).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(res, DEVICE_TOKEN, SESSION_TOKEN)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the client password', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.checkClientPassword).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(NOT_PASSWORD, FAKE_CLIENT)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with correct error', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.respond).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWithMatch(
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match({
|
||||||
|
code: '123',
|
||||||
|
info: 'One of many password errors'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
184
node_server/ComServe/hJSON/specs/RedeemPaycode.spec.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* Unit testing file for RedeemPaycode command
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
/* eslint max-nested-callbacks: ["error", 5] */
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const testGlobals = require('../../../tools/test/testGlobals.js');
|
||||||
|
const chai = require('chai');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const sinonChai = require('sinon-chai');
|
||||||
|
const chaiAsPromised = require('chai-as-promised');
|
||||||
|
const rewire = require('rewire');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `rewire` instead of require so that we can access private functions for test
|
||||||
|
*/
|
||||||
|
const redeemPaycodeClass = rewire('../RedeemPayCode.js');
|
||||||
|
const authStub = redeemPaycodeClass.__get__('authP');
|
||||||
|
const implStub = redeemPaycodeClass.__get__('impl');
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
chai.use(sinonChai);
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a sample Client and Device object to return
|
||||||
|
*/
|
||||||
|
const DEVICE_TOKEN = 'abc123';
|
||||||
|
const SESSION_TOKEN = 'def456';
|
||||||
|
const CLIENT_EMAIL = 'a@example.com';
|
||||||
|
const ACCOUNTID = '58e3a700f50f21000166b890';
|
||||||
|
const PAYCODE = 'KCT9A';
|
||||||
|
|
||||||
|
const MERCHANTCOMMENT = 'You were served today by Stuey.';
|
||||||
|
const REQUESTAMOUNT = 399;
|
||||||
|
const REQUESTTIP = 1;
|
||||||
|
const LATITUDE = 0.0;
|
||||||
|
const LONGITUDE = 0.0;
|
||||||
|
|
||||||
|
const FAKE_CLIENT = {
|
||||||
|
ClientName: CLIENT_EMAIL
|
||||||
|
};
|
||||||
|
const FAKE_DEVICE = {};
|
||||||
|
|
||||||
|
const SuccessReturn = {
|
||||||
|
code: '10020',
|
||||||
|
info: 'PayCode redeemed.',
|
||||||
|
TransactionID: '23N2O5D9'
|
||||||
|
};
|
||||||
|
const FailureReturn = {
|
||||||
|
code: '474',
|
||||||
|
info: 'DisplayName is invalid. Please fill out customer details.'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define some fake parameters
|
||||||
|
*/
|
||||||
|
const fakeFunctionInfo = {};
|
||||||
|
const fakeParameters = {};
|
||||||
|
const fakeHmacData = [];
|
||||||
|
|
||||||
|
const res = sinon.spy();
|
||||||
|
|
||||||
|
describe('RedeemPaycode', () => {
|
||||||
|
describe('with valid parameters', () => {
|
||||||
|
let callP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(authStub, 'validSession').resolves([FAKE_DEVICE, FAKE_CLIENT]);
|
||||||
|
sinon.stub(implStub, 'redeemPaycodeP').resolves(SuccessReturn);
|
||||||
|
sinon.stub(authStub, 'respond').returns();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
DeviceToken: DEVICE_TOKEN,
|
||||||
|
SessionToken: SESSION_TOKEN,
|
||||||
|
AccountID: ACCOUNTID,
|
||||||
|
PayCode: PAYCODE,
|
||||||
|
MerchantComment: MERCHANTCOMMENT,
|
||||||
|
RequestAmount: REQUESTAMOUNT,
|
||||||
|
RequestTip: REQUESTTIP,
|
||||||
|
Latitude: LATITUDE,
|
||||||
|
Longitude: LONGITUDE
|
||||||
|
};
|
||||||
|
|
||||||
|
callP = redeemPaycodeClass.process(res, fakeFunctionInfo, fakeParameters, testData, fakeHmacData);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
authStub.validSession.restore();
|
||||||
|
authStub.respond.restore();
|
||||||
|
implStub.redeemPaycodeP.restore();
|
||||||
|
});
|
||||||
|
it('runs', () => {
|
||||||
|
return expect(callP).to.eventually.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('validates the session', () => {
|
||||||
|
return callP.then(expect(authStub.validSession).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(res, DEVICE_TOKEN, SESSION_TOKEN));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.respond).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWithMatch(
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match({
|
||||||
|
code: '10020',
|
||||||
|
info: 'PayCode redeemed.'
|
||||||
|
})
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reponds with WARNING', () => {
|
||||||
|
let callP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(authStub, 'validSession').resolves([FAKE_DEVICE, FAKE_CLIENT]);
|
||||||
|
sinon.stub(implStub, 'redeemPaycodeP').rejects(FailureReturn);
|
||||||
|
sinon.stub(authStub, 'respond').returns();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
DeviceToken: DEVICE_TOKEN,
|
||||||
|
SessionToken: SESSION_TOKEN,
|
||||||
|
AccountID: ACCOUNTID,
|
||||||
|
PayCode: PAYCODE,
|
||||||
|
MerchantComment: MERCHANTCOMMENT,
|
||||||
|
RequestAmount: REQUESTAMOUNT,
|
||||||
|
RequestTip: REQUESTTIP,
|
||||||
|
Latitude: LATITUDE,
|
||||||
|
Longitude: LONGITUDE
|
||||||
|
};
|
||||||
|
|
||||||
|
callP = redeemPaycodeClass.process(res, fakeFunctionInfo, fakeParameters, testData, fakeHmacData);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
authStub.validSession.restore();
|
||||||
|
authStub.respond.restore();
|
||||||
|
implStub.redeemPaycodeP.restore();
|
||||||
|
});
|
||||||
|
it('runs', () => {
|
||||||
|
return expect(callP).to.eventually.be.fulfilled;
|
||||||
|
});
|
||||||
|
it('responds', () => {
|
||||||
|
return callP.then(() =>
|
||||||
|
expect(authStub.respond).to.have.been
|
||||||
|
.calledOnce
|
||||||
|
.calledWithMatch(
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match.any,
|
||||||
|
sinon.match({
|
||||||
|
code: '474',
|
||||||
|
info: 'DisplayName is invalid. Please fill out customer details.'
|
||||||
|
}),
|
||||||
|
sinon.match('WARNING')
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
68
node_server/ComServe/log.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Console Logging Functionality for Bridge Pay
|
||||||
|
* @preserve Copyright 2015 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes
|
||||||
|
*/
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object variables
|
||||||
|
*/
|
||||||
|
exports.verbose = 1; // Used to start/stop console logging. Set to 0 on release versions.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes info plus time stamp to both console and database. and log file.
|
||||||
|
* Entry type defines the message type: e.g. Info, Warning, Error, WWW etc.
|
||||||
|
*
|
||||||
|
* @see {@link http://10.0.10.242/w/tricore_architecture/database_design/collections/systemlog/}
|
||||||
|
*/
|
||||||
|
exports.system = function(entryClass, entryInfo, entryFunction, entryCode, entryUser, entrySource) {
|
||||||
|
/**
|
||||||
|
* @type {function} system
|
||||||
|
* @param {!string} entryClass - The type of event - e.g. INFO, ERROR, WARNING.
|
||||||
|
* @param {!string} entryInfo - A free text string giving more information on whatever happened.
|
||||||
|
* @param {!string} entryFunction - The function or module that generated the entry.
|
||||||
|
* @param {?string} entryCode - An error code if appropriate (blank otherwise).
|
||||||
|
* @param {?string} entryUser - The originating person 'client@me.com (0771823450)'.
|
||||||
|
* @param {!string} entrySource - The source that caused the event e.g. '86.118.232.2 (HTTPS:443)'.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Create the log data structure.
|
||||||
|
*/
|
||||||
|
var logData = {};
|
||||||
|
logData.DateTime = new Date();
|
||||||
|
logData.ServerID = config.CCServerName + ' (VIP ' + config.CCServerIP + ')'; // Note the Virtual IP can be used by more than one box.
|
||||||
|
logData.Class = entryClass;
|
||||||
|
logData.Function = entryFunction;
|
||||||
|
logData.Code = entryCode;
|
||||||
|
logData.Info = entryInfo;
|
||||||
|
logData.User = entryUser;
|
||||||
|
logData.Source = entrySource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the system is in verbose mode, output to the console.
|
||||||
|
*/
|
||||||
|
if (exports.verbose) {
|
||||||
|
var consoleOutput = '[' + logData.DateTime.toISOString() + ' ' + logData.ServerID + '] ';
|
||||||
|
consoleOutput += logData.Class + ' (' + logData.Function;
|
||||||
|
if (entryCode !== '') {
|
||||||
|
consoleOutput += (', ' + entryCode);
|
||||||
|
}
|
||||||
|
consoleOutput += ') from ' + logData.User + ' at ' + logData.Source + ': ' + logData.Info;
|
||||||
|
console.log(consoleOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the object to the system log.
|
||||||
|
*/
|
||||||
|
if (mainDB.dbOnline) {
|
||||||
|
mainDB.addObject(mainDB.collectionSystemLog, logData, undefined, false, null);
|
||||||
|
}
|
||||||
|
};
|
15
node_server/ComServe/mailer-promises.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @file This file wraps the functions in mailer.js with promises for simpler
|
||||||
|
* use in promises and async/await
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Q = require('q');
|
||||||
|
const mailer = require('./mailer.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendEmail: (...args) => Q.nfapply(mailer.sendEmail, args),
|
||||||
|
sendEmailByID: (...args) => Q.nfapply(mailer.sendEmailByID, args),
|
||||||
|
sendWelcomeEmail: (...args) => Q.nfapply(mailer.sendWelcomeEmail, args),
|
||||||
|
sendEmailChangedEmails: (...args) => Q.nfapply(mailer.sendEmailChangedEmails, args),
|
||||||
|
sendEmailRevertedEmails: (...args) => Q.nfapply(mailer.sendEmailRevertedEmails, args)
|
||||||
|
};
|
341
node_server/ComServe/mailer.js
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Comcarde Node.js Mailer Functionality
|
||||||
|
// Provides -Bridge- pay functionality.
|
||||||
|
// Copyright 2014 Comcarde
|
||||||
|
// Written by Keith Symington
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Includes
|
||||||
|
var nodemailer = require('nodemailer');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var Q = require('q');
|
||||||
|
var templates = require(global.pathPrefix + '../utils/templates.js');
|
||||||
|
var formattingUtils = require(global.pathPrefix + '../utils/formatting.js');
|
||||||
|
var references = require(global.pathPrefix + '../utils/references.js');
|
||||||
|
var debug = require('debug')('utils:mailer');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the exports
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
sendEmail: sendEmail,
|
||||||
|
sendEmailByID: sendEmailByID,
|
||||||
|
sendWelcomeEmail: sendWelcomeEmail,
|
||||||
|
sendEmailChangedEmails: sendEmailChangedEmails,
|
||||||
|
sendEmailRevertedEmails: sendEmailRevertedEmails
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the email transport options
|
||||||
|
*/
|
||||||
|
const TRANSPORTER = nodemailer.createTransport({
|
||||||
|
service: 'Gmail',
|
||||||
|
auth: {
|
||||||
|
user: 'admin@comcarde.com',
|
||||||
|
pass: 'xnasacgwvfvskvlj'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic function to send emails
|
||||||
|
*
|
||||||
|
* @param {String} mode - 'Test' to not send
|
||||||
|
* @param {String} destination - email address to send to
|
||||||
|
* @param {String} subject - email subject
|
||||||
|
* @param {String} htmlBody - email body in prepared HTML
|
||||||
|
* @param {String} caller - name of the caller for logging purposes
|
||||||
|
* @param {function} [next] - callback for success (if needed)
|
||||||
|
*/
|
||||||
|
function sendEmail(mode, destination, subject, htmlBody, caller, next) {
|
||||||
|
// Create the log data structure.
|
||||||
|
var mailOptions = {
|
||||||
|
from: 'admin@comcarde.com', // sender address
|
||||||
|
to: destination, // list of receivers
|
||||||
|
subject: subject, // Subject line
|
||||||
|
html: htmlBody // html body
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mode !== 'Test') {
|
||||||
|
// Sent the e-mail using the transporter.
|
||||||
|
TRANSPORTER.sendMail(mailOptions, function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
('Unable to send e-mail. ' + err),
|
||||||
|
caller,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
if (next) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('E-mail sent to ' + destination + ' (' + info.response + ').'),
|
||||||
|
caller,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
if (next) {
|
||||||
|
next(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Simply call back in test mode.
|
||||||
|
log.system(
|
||||||
|
'WARNING',
|
||||||
|
('E-mail test to ' + destination + ' (e-mail not sent).'),
|
||||||
|
caller,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
if (next) {
|
||||||
|
next(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic function to send emails to a client identified by an ID.
|
||||||
|
* This will lookup the client's email address from the database and then pass
|
||||||
|
* it on to the basic function for completion.
|
||||||
|
*
|
||||||
|
* @param {String} mode - 'Test' to not send
|
||||||
|
* @param {String} clientID - ID of the client to send email to
|
||||||
|
* @param {String} subject - email subject
|
||||||
|
* @param {String} htmlBody - email body in prepared HTML
|
||||||
|
* @param {String} caller - name of the caller for logging purposes
|
||||||
|
* @param {function} [next] - callback for success (if needed)
|
||||||
|
*/
|
||||||
|
function sendEmailByID(mode, clientID, subject, htmlBody, caller, next) {
|
||||||
|
references.getEmailAddress(clientID)
|
||||||
|
.then(function(email) {
|
||||||
|
sendEmail(mode, email, subject, htmlBody, caller, next);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
('Unable to find client to send e-mail to. ' + err),
|
||||||
|
caller,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
if (next) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the welcome email to the client.
|
||||||
|
*
|
||||||
|
* @param {Client} newClient - The newly added client
|
||||||
|
* @param {String} mode - 'test' to not actually send the email
|
||||||
|
* @param {String} caller - The name of the caller for logging purposed
|
||||||
|
* @param {Function} next - Callback function for callers who don't want promises
|
||||||
|
*
|
||||||
|
* @returns {Promise} - A promise for the result of sending the email
|
||||||
|
*/
|
||||||
|
function sendWelcomeEmail(newClient, mode, caller, next) {
|
||||||
|
//
|
||||||
|
// Get the email parameters
|
||||||
|
//
|
||||||
|
var token = newClient.EMailValidationToken;
|
||||||
|
var email = newClient.ClientName;
|
||||||
|
var query = {
|
||||||
|
code: token,
|
||||||
|
email: email
|
||||||
|
};
|
||||||
|
var confirmUrl = formattingUtils.formatPortalUrl('confirmemail-link', query);
|
||||||
|
var denyEmailUrl = formattingUtils.formatPortalUrl('denyemail-link', query);
|
||||||
|
|
||||||
|
debug('- send welcome email to: [%s], token [%s] ', email, token);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render the email
|
||||||
|
//
|
||||||
|
var htmlEmail = templates.render(
|
||||||
|
'bridge-welcome',
|
||||||
|
{
|
||||||
|
emailValidationCode: token,
|
||||||
|
confirmEmailUrl: confirmUrl,
|
||||||
|
denyEmailUrl: denyEmailUrl
|
||||||
|
});
|
||||||
|
var subject = 'Welcome to Bridge';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Pass it to the mailer to send (wrapped in a Q.nfcall to turn it into
|
||||||
|
// a promise).
|
||||||
|
// When the promise completes, call any callback defined
|
||||||
|
//
|
||||||
|
return Q.nfcall(sendEmail, mode, email, subject, htmlEmail, caller)
|
||||||
|
.then(function() {
|
||||||
|
// Success has no return values
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (next) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
return Q.reject(err); // Pass on the error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the emails related to an email change.
|
||||||
|
* This sends an email to the old address so they can revert if neccessary,
|
||||||
|
* and an address to the new address to confirm the email
|
||||||
|
*
|
||||||
|
* @param {String} oldEmail - the old email address being changed away from
|
||||||
|
* @param {String} newEmail - the new email address being changed to
|
||||||
|
* @param {Object} revertToken - The token to use to revert the email change
|
||||||
|
* @param {String} revertToken.token - the token
|
||||||
|
* @param {Object} confirmToken - The token to use to confirm the new email address
|
||||||
|
* @param {String} confirmToken.token - the token
|
||||||
|
* @param {String} mode - 'test' to not actually send the email
|
||||||
|
* @param {String} caller - The name of the caller for logging purposed
|
||||||
|
* @param {Function} next - Callback function for callers who don't want promises
|
||||||
|
*
|
||||||
|
* @returns {Promise} - A promise for the result of sending the email
|
||||||
|
*/
|
||||||
|
function sendEmailChangedEmails(oldEmail, newEmail, revertToken, confirmToken, mode, caller, next) {
|
||||||
|
//
|
||||||
|
// Build the urls.
|
||||||
|
//
|
||||||
|
var revertQuery = {
|
||||||
|
code: revertToken.token,
|
||||||
|
email: oldEmail
|
||||||
|
};
|
||||||
|
var confirmQuery = {
|
||||||
|
code: confirmToken.token,
|
||||||
|
email: newEmail
|
||||||
|
};
|
||||||
|
var baseRevertUrl = formattingUtils.formatPortalUrl('revert-changed-email-link');
|
||||||
|
var revertUrl = formattingUtils.formatPortalUrl('revert-changed-email-link', revertQuery);
|
||||||
|
|
||||||
|
var confirmChangeUrl = formattingUtils.formatPortalUrl('confirmemail-link', confirmQuery);
|
||||||
|
|
||||||
|
debug('- send email changed emails to: [%s] -> [%s] ', oldEmail, newEmail);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render the emails
|
||||||
|
//
|
||||||
|
var revertEmailBody = templates.render(
|
||||||
|
'email-changed-old',
|
||||||
|
{
|
||||||
|
oldEmail: oldEmail,
|
||||||
|
newEmail: newEmail,
|
||||||
|
revertEmailChangeUrl: revertUrl,
|
||||||
|
revertEmailChangeBaseUrl: baseRevertUrl,
|
||||||
|
revertValidationCode: revertQuery.code
|
||||||
|
});
|
||||||
|
var revertEmailSubject = 'Important: Email changed on Bridge Account';
|
||||||
|
|
||||||
|
var confirmEmailBody = templates.render(
|
||||||
|
'email-changed-new',
|
||||||
|
{
|
||||||
|
confirmChangedEmailUrl: confirmChangeUrl,
|
||||||
|
emailValidationCode: confirmQuery.code
|
||||||
|
});
|
||||||
|
var confirmEmailSubject = 'Please confirm your email address';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Pass it to the mailer to send (wrapped in a Q.nfcall to turn it into
|
||||||
|
// a promise).
|
||||||
|
// When the promise completes, call any callback defined
|
||||||
|
//
|
||||||
|
var sendRevertEmail = Q.nfcall(
|
||||||
|
sendEmail,
|
||||||
|
mode,
|
||||||
|
oldEmail,
|
||||||
|
revertEmailSubject,
|
||||||
|
revertEmailBody,
|
||||||
|
caller
|
||||||
|
);
|
||||||
|
var sendConfirmEmail = Q.nfcall(
|
||||||
|
sendEmail,
|
||||||
|
mode,
|
||||||
|
newEmail,
|
||||||
|
confirmEmailSubject,
|
||||||
|
confirmEmailBody,
|
||||||
|
caller
|
||||||
|
);
|
||||||
|
return Q.all([sendRevertEmail, sendConfirmEmail])
|
||||||
|
.then(function() {
|
||||||
|
// Success has no return values
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (next) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
return Q.reject(err); // Pass on the error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the emails related to an email change.
|
||||||
|
* This sends an email to the old address so they can revert if neccessary,
|
||||||
|
* and an address to the new address to confirm the email
|
||||||
|
*
|
||||||
|
* @param {String} revertToEmail - the old email address being reverted back to
|
||||||
|
* @param {String} revertFromEmail - the new email address being reverted from
|
||||||
|
* @param {String} mode - 'test' to not actually send the email
|
||||||
|
* @param {String} caller - The name of the caller for logging purposed
|
||||||
|
* @param {Function} next - Callback function for callers who don't want promises
|
||||||
|
*
|
||||||
|
* @returns {Promise} - A promise for the result of sending the email
|
||||||
|
*/
|
||||||
|
function sendEmailRevertedEmails(revertToEmail, revertFromEmail, mode, caller, next) {
|
||||||
|
debug('- send email reverted emails to: [%s] -> [%s] ', revertToEmail, revertFromEmail);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render the emails
|
||||||
|
//
|
||||||
|
var revertToEmailBody = templates.render('email-reverted-to', {});
|
||||||
|
var revertToEmailSubject = 'Email change reverted on Bridge Account';
|
||||||
|
|
||||||
|
var revertFromEmailBody = templates.render('email-reverted-from', {});
|
||||||
|
var revertFromEmailSubject = 'Email change reverted on Bridge Account';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Pass it to the mailer to send (wrapped in a Q.nfcall to turn it into
|
||||||
|
// a promise).
|
||||||
|
// When the promise completes, call any callback defined
|
||||||
|
//
|
||||||
|
var sendRevertToEmail = Q.nfcall(
|
||||||
|
sendEmail,
|
||||||
|
mode,
|
||||||
|
revertToEmail,
|
||||||
|
revertToEmailSubject,
|
||||||
|
revertToEmailBody,
|
||||||
|
caller
|
||||||
|
);
|
||||||
|
var sendRevertFromEmail = Q.nfcall(
|
||||||
|
sendEmail,
|
||||||
|
mode,
|
||||||
|
revertFromEmail,
|
||||||
|
revertFromEmailSubject,
|
||||||
|
revertFromEmailBody,
|
||||||
|
caller
|
||||||
|
);
|
||||||
|
return Q.all([sendRevertToEmail, sendRevertFromEmail])
|
||||||
|
.then(function() {
|
||||||
|
// Success has no return values
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
if (next) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
return Q.reject(err); // Pass on the error
|
||||||
|
});
|
||||||
|
}
|
76
node_server/ComServe/mainDB-promises.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* @file This file wraps the functions in mainDB.js with promises for simpler
|
||||||
|
* use in promises and async/await
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Q = require('q');
|
||||||
|
const httpStatus = require('http-status-codes');
|
||||||
|
|
||||||
|
//
|
||||||
|
// We MUST require maindDB with the exact same path as where it is initialised or we
|
||||||
|
// end up with a different instance of it where the collections have not been initialised.
|
||||||
|
//
|
||||||
|
const mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
const utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
findOneObject: (...args) => Q.nfapply(mainDB.findOneObject, args),
|
||||||
|
addObject: (...args) => Q.nfapply(mainDB.addObject, args),
|
||||||
|
addMany: (...args) => Q.nfapply(mainDB.addMany, args),
|
||||||
|
updateObject: (...args) => Q.nfapply(mainDB.updateObject, args),
|
||||||
|
removeObject: (...args) => Q.nfapply(mainDB.removeObject, args),
|
||||||
|
addObjectPWithCode: (...args) => withCode(module.exports.addObject, args),
|
||||||
|
findOneObjectPWithCode: (...args) => withCode(module.exports.findOneObject, args),
|
||||||
|
updateObjectPWithCode: (...args) => withCode(module.exports.updateObject, args),
|
||||||
|
removeObjectPWithCode: (...args) => withCode(module.exports.removeObject, args),
|
||||||
|
|
||||||
|
updateObjectPCheckObjectUpdated: (...args) => checkObjectUpdated(mainDB.updateObject, args),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share the mainDB file for easy access to the collections
|
||||||
|
*/
|
||||||
|
mainDB
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper functions that allows for specific error handling or promise functions
|
||||||
|
*
|
||||||
|
* @type {Function} withCode
|
||||||
|
* @param {!Function} action - function that this function has wrapped around
|
||||||
|
* @param {!Array} args - Options for the insert command. Use 'undefined' if there are none.
|
||||||
|
*/
|
||||||
|
function withCode(action, args) {
|
||||||
|
const code = args[args.length - 1];
|
||||||
|
const params = args.slice(0, args.length - 1);
|
||||||
|
|
||||||
|
return action(...params).catch(() =>
|
||||||
|
Q.reject(utils.createError(code, 'Database offline.', httpStatus.BAD_GATEWAY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific Wrapper for mongoDB update
|
||||||
|
* Handles general mongoDB errors and if the update fails to update any objects.
|
||||||
|
*
|
||||||
|
* @type {Function} checkObjectUpdated
|
||||||
|
* @param {!Function} action - function that this function has wrapped around
|
||||||
|
* @param {!Array} args - Options for the insert command. Use 'undefined' if there are none.
|
||||||
|
*/
|
||||||
|
function checkObjectUpdated(action, args) {
|
||||||
|
const code = args[args.length - 1];
|
||||||
|
const params = args.slice(0, args.length - 1);
|
||||||
|
return Q.nfcall(action, ...params)
|
||||||
|
.then((result) => {
|
||||||
|
if (result.result.nModified === 1) {
|
||||||
|
return Q.resolve(result);
|
||||||
|
} else {
|
||||||
|
return Q.reject(utils.createError(code, 'Failed to update object', httpStatus.CONFLICT));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.code && error.httpCode && error.message) {
|
||||||
|
return Q.reject(error);
|
||||||
|
} else {
|
||||||
|
return Q.reject(utils.createError(code, 'Database offline.', httpStatus.BAD_GATEWAY));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
2702
node_server/ComServe/mainDB.js
Normal file
314
node_server/ComServe/migrations.js
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Bridge Server data migrations functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Q = require('q');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var mongodb = require('mongodb');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var mainDB = require(global.pathPrefix + 'mainDB.js');
|
||||||
|
var utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
migrateClientNameToID: migrateClientNameToID
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function migrates the data such that the ClientName (i.e. email address)
|
||||||
|
* is no longer the foreign key in the related tables. This is changed to use
|
||||||
|
* the ClientID instead.
|
||||||
|
*/
|
||||||
|
function migrateClientNameToID() {
|
||||||
|
/**
|
||||||
|
* Find all the Clients without a ClientID, and give them one
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
ClientID: {$exists: false}
|
||||||
|
};
|
||||||
|
var projection = {
|
||||||
|
_id: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var deferAddComplete = Q.defer();
|
||||||
|
var addClientIdPromises = [];
|
||||||
|
|
||||||
|
mainDB.collectionClient.find(query)
|
||||||
|
.project(projection)
|
||||||
|
.forEach(
|
||||||
|
addIdToClient.bind(null, addClientIdPromises),
|
||||||
|
onAddIdDone.bind(null, deferAddComplete)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the iteration to complete,
|
||||||
|
* then wait for the updates to all complete,
|
||||||
|
* then start doing updates for all clients
|
||||||
|
*/
|
||||||
|
deferAddComplete.promise.then(function() {
|
||||||
|
Q.all(addClientIdPromises).then(function() {
|
||||||
|
doMigration();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for all clients that don't have an id.
|
||||||
|
* Used to add a unique id, then add all the other fields
|
||||||
|
*
|
||||||
|
* @param {Promise[]} promisesArray - array of promises to add our update to
|
||||||
|
* @param {Object} client - the value from the database
|
||||||
|
*/
|
||||||
|
function addIdToClient(promisesArray, client) {
|
||||||
|
var query = {
|
||||||
|
_id: client._id
|
||||||
|
};
|
||||||
|
var randomId = utils.timeBasedRandomCode();
|
||||||
|
var update = {
|
||||||
|
$set: {
|
||||||
|
ClientID: randomId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
promisesArray.push(
|
||||||
|
mainDB.collectionClient.updateOne(
|
||||||
|
query,
|
||||||
|
update
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the foreach iterator has gone through every client that needs
|
||||||
|
* an ID added
|
||||||
|
*
|
||||||
|
* @param {Defer} defer - a deffered promise for the completion of the foreach
|
||||||
|
* @param {any} err - any errors while doing the foreach
|
||||||
|
*/
|
||||||
|
function onAddIdDone(defer, err) {
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
'failed to iterate all clients needing ids',
|
||||||
|
'migrations.migrateClientNameToID',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
defer.reject(err);
|
||||||
|
} else {
|
||||||
|
// All passed
|
||||||
|
defer.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doMigration() {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Starting migration for ClientID',
|
||||||
|
'migrations.doMigration',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
/**
|
||||||
|
* Find all the Clients and update all the related tables
|
||||||
|
*/
|
||||||
|
var query = {
|
||||||
|
ClientID: {$exists: true}
|
||||||
|
};
|
||||||
|
var projection = {
|
||||||
|
ClientName: 1,
|
||||||
|
ClientID: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var deferUpdateComplete = Q.defer();
|
||||||
|
var updatePromises = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of collections to change. Format is either:
|
||||||
|
* {String} - Name of the collection, with default ClientName -> ClientID conversion
|
||||||
|
* {String[]} - Name of collection, Name of existing Email field, Name of new ID field
|
||||||
|
*/
|
||||||
|
const collectionsToChange = [
|
||||||
|
'Account',
|
||||||
|
'AccountArchive',
|
||||||
|
'Addresses',
|
||||||
|
'AddressArchive',
|
||||||
|
'BridgeLogin',
|
||||||
|
'Device',
|
||||||
|
'DeviceArchive',
|
||||||
|
'Images',
|
||||||
|
'Items',
|
||||||
|
'Messages',
|
||||||
|
'MessagesArchive',
|
||||||
|
'PayCode',
|
||||||
|
['Transaction', 'CustomerClientName', 'CustomerClientID'],
|
||||||
|
['Transaction', 'MerchantClientName', 'MerchantClientID'],
|
||||||
|
['TransactionArchive', 'CustomerClientName', 'CustomerClientID'],
|
||||||
|
['TransactionArchive', 'MerchantClientName', 'MerchantClientID'],
|
||||||
|
'TransactionHistory'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create bulk operations for all collections
|
||||||
|
*/
|
||||||
|
var ops = createBulkOps(collectionsToChange);
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Created [' + Object.keys(ops).length + '] bulk operations',
|
||||||
|
'migrations.doMigration',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
|
||||||
|
mainDB.collectionClient.find(query)
|
||||||
|
.project(projection)
|
||||||
|
.forEach(
|
||||||
|
createClientOps.bind(null, collectionsToChange, ops),
|
||||||
|
runOps.bind(null,ops)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the bulk operations that we will update with all the changes we need
|
||||||
|
* to do.
|
||||||
|
*
|
||||||
|
* @param {(String|String[])[]} collectionsToChange - the list of collections to chnage
|
||||||
|
*
|
||||||
|
* @returns {Object} - object with key = collection name, value = UnorderedBulkOperation
|
||||||
|
*/
|
||||||
|
function createBulkOps(collectionsToChange) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Initializing [' + collectionsToChange.length + '] bulk operations',
|
||||||
|
'migrations.createBulkOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
|
||||||
|
var ops = {};
|
||||||
|
|
||||||
|
for (var i = 0; i < collectionsToChange.length; ++i) {
|
||||||
|
var collName = collectionsToChange[i];
|
||||||
|
if (_.isArray(collName)) {
|
||||||
|
collName = collName[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var collection = mainDB['collection' + collName];
|
||||||
|
var bulkOp = collection.initializeUnorderedBulkOp();
|
||||||
|
ops[collName] = bulkOp;
|
||||||
|
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
' - initialized bulk op for [' + collName + ']',
|
||||||
|
'migrations.createBulkOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
}
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates all the bulk operations
|
||||||
|
*
|
||||||
|
* @param {(String|String[])[]} collectionsToChange - the list of collections to change
|
||||||
|
* @param {UnorderedBulkOperation[]} ops - the list of operations to add to
|
||||||
|
* @param {Object} client - the client to create ops for
|
||||||
|
*/
|
||||||
|
function createClientOps(collectionsToChange, ops, client) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Creating update ops for [' + client.ClientName + '] -> [' + client.ClientID + ']',
|
||||||
|
'migrations.createClientOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
|
||||||
|
for (var i = 0; i < collectionsToChange.length; ++i) {
|
||||||
|
var collName = collectionsToChange[i];
|
||||||
|
var srcName = 'ClientName';
|
||||||
|
var destName = 'ClientID';
|
||||||
|
if (_.isArray(collName)) {
|
||||||
|
collName = collectionsToChange[i][0];
|
||||||
|
srcName = collectionsToChange[i][1];
|
||||||
|
destName = collectionsToChange[i][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all records that have the specified ClientName
|
||||||
|
var query = {};
|
||||||
|
query[srcName] = client.ClientName;
|
||||||
|
|
||||||
|
// Update with the relevant ClientID, and remove the ClientName
|
||||||
|
var update = {
|
||||||
|
$set: {},
|
||||||
|
$unset: {}
|
||||||
|
};
|
||||||
|
update.$unset[srcName] = '';
|
||||||
|
update.$set[destName] = client.ClientID;
|
||||||
|
ops[collName].find(query).update(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the update operations
|
||||||
|
*
|
||||||
|
* @param {UnorderedBulkOperation[]} ops - the list of operations to add to
|
||||||
|
* @param {varies} err - any errors
|
||||||
|
*/
|
||||||
|
function runOps(ops, err) {
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
'failed to create all bulk operations',
|
||||||
|
'migrations.runOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var opsPromises = [];
|
||||||
|
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
'Executing [' + Object.keys(ops).length + '] bulk operation',
|
||||||
|
'migrations.runOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
/**
|
||||||
|
* Execute all the operations
|
||||||
|
*/
|
||||||
|
_.forEach(ops, function(val, key) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
' - executing bulk operation for: ' + key,
|
||||||
|
'migrations.runOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
opsPromises.push(val.execute({
|
||||||
|
fsync: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
Q.all(opsPromises)
|
||||||
|
.then(function() {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
'Migration to ClientID complete successfully!',
|
||||||
|
'migrations.runOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
'failed to run all update operations',
|
||||||
|
'migrations.runOps',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return Q.reject(err);
|
||||||
|
});
|
||||||
|
}
|
99
node_server/ComServe/rate_limit.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Rate limit functions for the app API
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Richard Taylor
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*
|
||||||
|
* Defines rate limits for the app API, and a default rate limit for any other
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
var _ = require('lodash');
|
||||||
|
var RateLimit = require('express-rate-limit');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobiles are frequently behind NAT, so we combine IP address + port number
|
||||||
|
* to give a good approximation of who a remote connection is related to.
|
||||||
|
* Note that the firewalls all seem to populate different information, the remotePort
|
||||||
|
* being the most difficult to verify. Therefore the rate limiter will only work correctly
|
||||||
|
* if the connection is deemed secure.
|
||||||
|
*
|
||||||
|
* @param {object} req - The request object
|
||||||
|
*
|
||||||
|
* @returns {string} - The key to use to identify this client
|
||||||
|
*/
|
||||||
|
function rateLimitByIpAndPort(req) {
|
||||||
|
if (req.secure) {
|
||||||
|
/**
|
||||||
|
* If the request is coming from a secure source, we can trust the headers.
|
||||||
|
* Azure and Bluemix are special cases as the info is put elsewhere.
|
||||||
|
*/
|
||||||
|
switch (global.CURRENT_DEPLOYMENT_ENV) {
|
||||||
|
case 'Azure':
|
||||||
|
if (req.headers.hasOwnProperty('x-forwarded-for')) {
|
||||||
|
return (req.headers['x-forwarded-for'].split(':')[0] + '-' + req.headers['x-forwarded-for'].split(':')[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Bluemix':
|
||||||
|
if (req.headers.hasOwnProperty('$wsra')) {
|
||||||
|
return (req.headers.$wsra + '-' + req.connection.remotePort);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request is not secure or no headers are present. The default action should be used.
|
||||||
|
* This may result in limiting problems in new environments if the configuration requires
|
||||||
|
* a special case as shown above.
|
||||||
|
*/
|
||||||
|
return (req.ip + '-' + req.connection.remotePort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rate limiting for the app API
|
||||||
|
* Warning: we must clone the value from config so that when we change the
|
||||||
|
* keyGenerator etc. it doesn't affect other places using the same
|
||||||
|
* config.
|
||||||
|
*/
|
||||||
|
var rateLimitConfig = _.clone(config.rateLimits.api);
|
||||||
|
rateLimitConfig.keyGenerator = rateLimitByIpAndPort;
|
||||||
|
rateLimitConfig.handler = jsonResponse;
|
||||||
|
var limiterApi = new RateLimit(rateLimitConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a JSON response if the limit is reached
|
||||||
|
*
|
||||||
|
* @param {Object} req - The request object
|
||||||
|
* @param {Object} res - The response object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function jsonResponse(req, res) {
|
||||||
|
res.status(rateLimitConfig.statusCode).json({
|
||||||
|
code: 465,
|
||||||
|
info: 'Rate limit reached. Please wait and try again.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rate limiting for everything else
|
||||||
|
* Warning: we must clone the value from config so that when we change the
|
||||||
|
* keyGenerator etc. it doesn't affect other places using the same
|
||||||
|
* config.
|
||||||
|
*/
|
||||||
|
var rateLimitConfigDefault = _.clone(config.rateLimits.fallback);
|
||||||
|
rateLimitConfigDefault.keyGenerator = rateLimitByIpAndPort;
|
||||||
|
var limiterDefault = new RateLimit(rateLimitConfigDefault);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to insert the rate limiting middleware into the chain for the
|
||||||
|
* appropriate paths
|
||||||
|
*
|
||||||
|
* @param {object} server - The https server to connect the middleware to
|
||||||
|
*/
|
||||||
|
exports.enableLimits = function(server) {
|
||||||
|
server.use(/\/server_post/i, limiterApi);
|
||||||
|
server.use(/\/(?!server_post).*/i, limiterDefault); // Everything except /server_post
|
||||||
|
};
|
11
node_server/ComServe/sms-promises.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @file This file wraps the functions in mailer.js with promises for simpler
|
||||||
|
* use in promises and async/await
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Q = require('q');
|
||||||
|
const sms = require('./sms.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendSMS: (...args) => Q.nfapply(sms.sendSMS, args)
|
||||||
|
};
|
121
node_server/ComServe/sms.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Comcarde Node.js SMS Compatibility with Textlocal.
|
||||||
|
// Provides -Bridge- pay functionality.
|
||||||
|
// Copyright 2014 Comcarde
|
||||||
|
// Written by Keith Symington
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Includes
|
||||||
|
var https = require('https');
|
||||||
|
var querystring = require('querystring');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var adminNotifier = require(global.pathPrefix + '../utils/adminNotifier.js');
|
||||||
|
|
||||||
|
// SMS setup code.
|
||||||
|
exports.smsPart1 = '/send/?username=admin@comcarde.com&hash=0d0781473c9df47b3cce91d5f71d9d958eed6e3a&numbers=';
|
||||||
|
exports.smsPart2 = '&message=';
|
||||||
|
exports.smsPart3 = '&sender=Bridge';
|
||||||
|
exports.smsTest = '&test=true';
|
||||||
|
exports.smsTestMode = false;
|
||||||
|
exports.smsCredits = -1;
|
||||||
|
exports.adminMobile = '07713904702'; // Keith Symington
|
||||||
|
exports.backupMobile = '07789191413'; // Tom Mathews
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Sends an SMS message to defined number using a TextLocal account.
|
||||||
|
// Uses SSL. The function needs to be passed a callback. The callback
|
||||||
|
// will be given two arguments: the first is an error, the second
|
||||||
|
// is the number of texts remaining in the balance.
|
||||||
|
// This function is silent and does not log to console.
|
||||||
|
// The system should automatically add escape characters (%20) -
|
||||||
|
// if this fails the error 'No sender name was specified' will be returned via callback.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
exports.sendSMS = function(mode, number, message, next) {
|
||||||
|
// Local variables.
|
||||||
|
var fullQuery = '';
|
||||||
|
var returnedData = '';
|
||||||
|
|
||||||
|
// Check for test mode.
|
||||||
|
if (mode !== 'Test') {
|
||||||
|
// Check for available credits.
|
||||||
|
if (exports.smsCredits === 0) {
|
||||||
|
next('No SMS credits with Txtlocal.', 0);
|
||||||
|
} else {
|
||||||
|
// When in test mode, add the test identifier.
|
||||||
|
if (exports.smsTestMode) {
|
||||||
|
fullQuery = exports.smsPart1 + number + exports.smsPart2 + querystring.escape(message) + exports.smsPart3 + exports.smsTest;
|
||||||
|
} else {
|
||||||
|
fullQuery = exports.smsPart1 + number + exports.smsPart2 + querystring.escape(message) + exports.smsPart3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up query.
|
||||||
|
var smsOptions = {
|
||||||
|
host: 'api.txtlocal.com',
|
||||||
|
port: 443,
|
||||||
|
path: fullQuery
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initiate the get request.
|
||||||
|
https.get(smsOptions, function(result) {
|
||||||
|
if (result.statusCode !== 200) {
|
||||||
|
next('HTTPS error when trying to reach Textlocal servers.', null);
|
||||||
|
} else {
|
||||||
|
// Data indicates that information is still arriving.
|
||||||
|
result.on('data', function(chunk) {
|
||||||
|
returnedData += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
// End indicates that everything has been read.
|
||||||
|
result.on('end', function() { // Try to parse the data to see if it is JSON.
|
||||||
|
var returnedJSON = JSON.parse(returnedData);
|
||||||
|
// Check the status of the SMS send.
|
||||||
|
if (returnedJSON.status !== 'success') {
|
||||||
|
// SMS send failed.
|
||||||
|
next(returnedJSON.status, returnedJSON.balance);
|
||||||
|
} else {
|
||||||
|
// SMS send successful.
|
||||||
|
exports.smsCredits = returnedJSON.balance;
|
||||||
|
// Warning text for low credit. Throw away callback.
|
||||||
|
if (exports.smsCredits === 10) {
|
||||||
|
exports.sendSMS(null, (exports.adminMobile + ',' + exports.backupMobile),
|
||||||
|
'System Warning: Txtlocal SMS credits nearly exhausted.', function(err, smsBalance) {
|
||||||
|
// Check for errors.
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'CRITICAL',
|
||||||
|
('Cannot send SMS. ' + err),
|
||||||
|
'sms.sendSMS',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
} else {
|
||||||
|
log.system(
|
||||||
|
'ERROR',
|
||||||
|
('Txtlocal SMS credits now exhausted (' + smsBalance + ').'),
|
||||||
|
'sms.sendSMS',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Also notify via adminNotifier
|
||||||
|
//
|
||||||
|
adminNotifier.notifyCredits('txtlocal', returnedJSON.balance);
|
||||||
|
|
||||||
|
// Success!!!! Send callback.
|
||||||
|
next(null, returnedJSON.balance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).on('error', function(err) {
|
||||||
|
next(err, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Simply call back in test mode.
|
||||||
|
next(null, null);
|
||||||
|
}
|
||||||
|
};
|
178
node_server/ComServe/specs/mainDB-promises.spec.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* Unit testing file for mainDB
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
/* eslint max-nested-callbacks: ["error", 5] */
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const testGlobals = require('../../tools/test/testGlobals.js');
|
||||||
|
const chai = require('chai');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const sinonChai = require('sinon-chai');
|
||||||
|
const chaiAsPromised = require('chai-as-promised');
|
||||||
|
const rewire = require('rewire');
|
||||||
|
const httpStatus = require('http-status-codes');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `rewire` instead of require so that we can access private functions for test
|
||||||
|
*/
|
||||||
|
const mainDBPromisesClass = rewire('../mainDB-promises.js');
|
||||||
|
const maindDBStub = mainDBPromisesClass.__get__('mainDB');
|
||||||
|
const sandbox = sinon.createSandbox();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
chai.use(sinonChai);
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
describe('mainDB', () => {
|
||||||
|
/**
|
||||||
|
* After each tests, reset the stubs.
|
||||||
|
*/
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
describe('calls "checkObjectUpdated"', () => {
|
||||||
|
describe('which succeeds', () => {
|
||||||
|
let returnValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(maindDBStub, 'updateObject').callsArgWith(4, null, {result: {nModified: 1}});
|
||||||
|
|
||||||
|
returnValue = await mainDBPromisesClass.updateObjectPCheckObjectUpdated(1, 2, 3, 4, 5);
|
||||||
|
});
|
||||||
|
it('called with correct params', () => {
|
||||||
|
return expect(maindDBStub.updateObject)
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(
|
||||||
|
1, 2, 3, 4
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('it returns with the return value', () => {
|
||||||
|
return expect(returnValue).eql(
|
||||||
|
{
|
||||||
|
result: {
|
||||||
|
nModified: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('which fails', () => {
|
||||||
|
describe('general monoDB error', () => {
|
||||||
|
let errorValue;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(maindDBStub, 'updateObject').callsArgWith(4, 'mongo error');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mainDBPromisesClass.updateObjectPCheckObjectUpdated(1, 2, 3, 4, 5);
|
||||||
|
} catch (error) {
|
||||||
|
errorValue = error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('called with correct params', () => {
|
||||||
|
return expect(maindDBStub.updateObject)
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(
|
||||||
|
1, 2, 3, 4
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('it returns with correct error code', () => {
|
||||||
|
return expect(errorValue).to.eql({
|
||||||
|
code: 5,
|
||||||
|
message: 'Database offline.',
|
||||||
|
httpCode: httpStatus.BAD_GATEWAY
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('failed to update any objects', () => {
|
||||||
|
let errorValue;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(maindDBStub, 'updateObject').callsArgWith(4, null, {result: {nModified: 0}});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mainDBPromisesClass.updateObjectPCheckObjectUpdated(1, 2, 3, 4, 5);
|
||||||
|
} catch (error) {
|
||||||
|
errorValue = error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('called with correct params', () => {
|
||||||
|
return expect(maindDBStub.updateObject)
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(
|
||||||
|
1, 2, 3, 4
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('it returns with correct error code', () => {
|
||||||
|
return expect(errorValue).to.eql({
|
||||||
|
code: 5,
|
||||||
|
message: 'Failed to update object',
|
||||||
|
httpCode: httpStatus.CONFLICT
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('calls "withCode"', () => {
|
||||||
|
describe('which succeeds', () => {
|
||||||
|
let returnValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(mainDBPromisesClass, 'addObject').resolves({aValue: 5});
|
||||||
|
|
||||||
|
returnValue = await mainDBPromisesClass.addObjectPWithCode(1, 2, 3, 4, 5);
|
||||||
|
});
|
||||||
|
it('called with correct params', () => {
|
||||||
|
return expect(mainDBPromisesClass.addObject)
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(
|
||||||
|
1, 2, 3, 4
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('it returns with the return value', () => {
|
||||||
|
return expect(returnValue).to.eql(
|
||||||
|
{aValue: 5}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('which fails', () => {
|
||||||
|
let errorValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before each test, set up the tests stubs to return the controlled data,
|
||||||
|
* then call the function we are testing.
|
||||||
|
*/
|
||||||
|
beforeEach(async () => {
|
||||||
|
sandbox.stub(mainDBPromisesClass, 'addObject').rejects();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mainDBPromisesClass.addObjectPWithCode(1, 2, 3, 4, 5);
|
||||||
|
} catch (error) {
|
||||||
|
errorValue = error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('called with correct params', () => {
|
||||||
|
return expect(mainDBPromisesClass.addObject)
|
||||||
|
.calledOnce
|
||||||
|
.calledWith(
|
||||||
|
1, 2, 3, 4
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('it returns with correct error code', () => {
|
||||||
|
return expect(errorValue).to.eql({
|
||||||
|
code: 5,
|
||||||
|
message: 'Database offline.',
|
||||||
|
httpCode: httpStatus.BAD_GATEWAY
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
685
node_server/ComServe/specs/utils.spec.js
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
/* eslint-disable max-nested-callbacks */
|
||||||
|
/* eslint-disable mocha/no-hooks-for-single-case */
|
||||||
|
/* eslint-disable max-len*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
const chai = require('chai');
|
||||||
|
const sinonChai = require('sinon-chai');
|
||||||
|
const chaiAsPromised = require('chai-as-promised');
|
||||||
|
const rewire = require('rewire');
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
|
require('../../tools/test/testGlobals');
|
||||||
|
|
||||||
|
const utils = rewire('../utils');
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
chai.use(sinonChai);
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
const DATA_STRING = 'someData';
|
||||||
|
const KEY = '1';
|
||||||
|
const CLIENT_ID = '123456789012345678901234';
|
||||||
|
const INVALID_CLIENT_ID = '1234567890123456789012345';
|
||||||
|
const DIFFERENT_CLIENT_ID = '123456789012345678900000';
|
||||||
|
const ENCRYPTED_DATA = '3::9b357eaf82b6e86f8bfc8761a6fd2e3f6787b4e96f7e228388fb70a8e1d5b6f9a0f7ea03f3e9f19684a6c2cd97ebeba85d45c77c05c2f3b9adba878f84b86a95';
|
||||||
|
const ENCRYPTED_DATA_BAD_FORMAT = '3117354dce8e95918cc6cd5ad7932d258b2dd9558e0a613a2825fe54d36528d22c58c9a75c4d62c8adabc73a345f31c899a9cf9b12ee66489cd9b71f29b1fa2cf81';
|
||||||
|
const ENCRYPTED_DATA_WRONG_VERSION = '1::7354dce8e95918cc6cd5ad7932d258b2dd9558e0a613a2825fe54d36528d22c58c9a75c4d62c8adabc73a345f31c899a9cf9b12ee66489cd9b71f29b1fa2cf81';
|
||||||
|
|
||||||
|
describe('ComServe.utils', () => {
|
||||||
|
describe('calls encryptDataV3', () => {
|
||||||
|
it('returns a different value when encrypting identical data', () => {
|
||||||
|
const firstValue = utils.encryptDataV3(DATA_STRING, KEY, CLIENT_ID);
|
||||||
|
const secondValue = utils.encryptDataV3(DATA_STRING, KEY, CLIENT_ID);
|
||||||
|
return expect(firstValue).to.not.equal(secondValue);
|
||||||
|
});
|
||||||
|
describe('returns with an error', () => {
|
||||||
|
it('Nothing to encrypt.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(null, KEY, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(1, 'Nothing to encrypt.'));
|
||||||
|
});
|
||||||
|
it('No client key.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(DATA_STRING, null, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(2, 'No client key.'));
|
||||||
|
});
|
||||||
|
it('No client ID.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(DATA_STRING, KEY, null);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(3, 'No client ID.'));
|
||||||
|
});
|
||||||
|
it('Invalid client ID.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(DATA_STRING, KEY, INVALID_CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(3, 'Client Id length must be 24'));
|
||||||
|
});
|
||||||
|
it('Data to encrypt must be a string.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3({}, KEY, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(4, 'Data to encrypt must be a string.'));
|
||||||
|
});
|
||||||
|
it('Client key must be a string.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(DATA_STRING, {}, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(5, 'Client key must be a string.'));
|
||||||
|
});
|
||||||
|
it('Client Key must be in Hex', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(DATA_STRING, 'z', CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(5, 'Client Key must be in Hex'));
|
||||||
|
});
|
||||||
|
it('Client ID must be a string.', () => {
|
||||||
|
const returnValue = utils.encryptDataV3(DATA_STRING, KEY, {});
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(6, 'Client ID must be a string.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('calls decryptDataV3', () => {
|
||||||
|
it('returns the correct decrypted data after encrypting the data', () => {
|
||||||
|
const decryptedData = utils.decryptDataV3(ENCRYPTED_DATA, KEY, CLIENT_ID);
|
||||||
|
return expect(decryptedData).to.equal(DATA_STRING);
|
||||||
|
});
|
||||||
|
describe('returns with an error', () => {
|
||||||
|
it('Nothing to decrypt.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(null, KEY, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(1, 'Nothing to decrypt.'));
|
||||||
|
});
|
||||||
|
it('No client key.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA, null, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(2, 'No client key.'));
|
||||||
|
});
|
||||||
|
it('No client ID.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA, KEY, null);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(3, 'No client ID.'));
|
||||||
|
});
|
||||||
|
it('Data to encrypt must be a string.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3({}, KEY, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(4, 'Data to be decrypted must be a string.'));
|
||||||
|
});
|
||||||
|
it('Client key must be a string.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA, {}, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(5, 'Client key must be a string.'));
|
||||||
|
});
|
||||||
|
it('Client ID must be a string.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA, KEY, {});
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(6, 'Client ID must be a string.'));
|
||||||
|
});
|
||||||
|
it('Encrypted data did not contain the 2 expected elements.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA_BAD_FORMAT, KEY, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(7, 'Encrypted data did not contain the 2 expected elements.'));
|
||||||
|
});
|
||||||
|
it('Unexpected encryption version.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA_WRONG_VERSION, KEY, CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(8, 'Unexpected encryption version.'));
|
||||||
|
});
|
||||||
|
it('Information does not belong to client.', () => {
|
||||||
|
const returnValue = utils.decryptDataV3(ENCRYPTED_DATA, KEY, DIFFERENT_CLIENT_ID);
|
||||||
|
return expect(returnValue).to.deep.equal(utils.createError(12, 'Information does not belong to client.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('calls identifyCard', () => {
|
||||||
|
it('returns ISO / TC68 Card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('0000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '0*** **** **** 0000',
|
||||||
|
type: 'ISO / TC68 Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Airline/UATP', () => {
|
||||||
|
const cardDetails = utils.identifyCard('1000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '1*** **** **** 0000',
|
||||||
|
type: 'Airline/UATP',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club enRoute, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2014000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'Diners Club enRoute',
|
||||||
|
icon: 'Diners-Generic.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club enRoute, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2149000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'Diners Club enRoute',
|
||||||
|
icon: 'Diners-Generic.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MIR, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2204000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'MIR',
|
||||||
|
icon: 'MIR.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MIR, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2200000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'MIR',
|
||||||
|
icon: 'MIR.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Mastercard, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2720000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'Mastercard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Mastercard, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2221000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'Mastercard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Airline/UATP/Other Card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('2000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '2*** **** **** 0000',
|
||||||
|
type: 'Airline/UATP/Other Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns American Express, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3400000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'American Express',
|
||||||
|
icon: 'AMEX.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns American Express, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3700000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'American Express',
|
||||||
|
icon: 'AMEX.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club International, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3600000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Diners Club International',
|
||||||
|
icon: 'DINERS.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club International, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3800000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Diners Club International',
|
||||||
|
icon: 'DINERS.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club International, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3900000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Diners Club International',
|
||||||
|
icon: 'DINERS.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club International, try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3090000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Diners Club International',
|
||||||
|
icon: 'DINERS.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Diners Club',
|
||||||
|
icon: 'DINERS.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Diners Club, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3050000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Diners Club',
|
||||||
|
icon: 'DINERS.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns JCB. try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3589000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'JCB',
|
||||||
|
icon: 'JCB.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns JCB. try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3528000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'JCB',
|
||||||
|
icon: 'JCB.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Other Card, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('3590000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '3*** **** **** 0000',
|
||||||
|
type: 'Other Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Dankort, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4175000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Dankort',
|
||||||
|
icon: 'Dankort.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Dankort, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4571000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Dankort',
|
||||||
|
icon: 'Dankort.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro , try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4903000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro , try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4905000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro , try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4911000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro , try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4936000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Visa', () => {
|
||||||
|
const cardDetails = utils.identifyCard('4000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '4*** **** **** 0000',
|
||||||
|
type: 'Visa',
|
||||||
|
icon: 'VISA_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MasterCard, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5100000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'MasterCard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MasterCard, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5200000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'MasterCard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MasterCard, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5300000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'MasterCard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MasterCard, try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5400000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'MasterCard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns MasterCard, try 5', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5500000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'MasterCard',
|
||||||
|
icon: 'MASTERCARD_CREDIT.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5600000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5700000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5800000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Dankort', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5019000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Dankort',
|
||||||
|
icon: 'Dankort.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Bankcard, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5610000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Bankcard',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns CardGuard', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5392000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'CardGuard',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 5', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5641820000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Bankcard, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5602210000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Bankcard',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Bankcard, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5602250000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Bankcard',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Verve, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5060990000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Verve',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Verve, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5061980000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Verve',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Other Card, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('5900000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '5*** **** **** 0000',
|
||||||
|
type: 'Other Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns China UnionPay', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6200000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'China UnionPay',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Discover Card, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6500000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Discover Card',
|
||||||
|
icon: 'Discover-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns InstaPayment, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6370000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'InstaPayment',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns InstaPayment, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6380000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'InstaPayment',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns InstaPayment, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6390000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'InstaPayment',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Discover Card. try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6440000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Discover Card',
|
||||||
|
icon: 'Discover-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Discover Card. try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6490000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Discover Card',
|
||||||
|
icon: 'Discover-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns InterPayment Card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6360000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'InterPayment Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Discover Card. try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6011000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Discover Card',
|
||||||
|
icon: 'Discover-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Laser, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6304000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Laser',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Laser, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6706000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Laser',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Laser, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6771000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Laser',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Laser, try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6709000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Laser',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Solo, try 1', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6334000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Solo',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Solo, try 2', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6767000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Solo',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 6', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6333000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 7', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6759000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Verve, try 3', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6500020000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Verve',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Verve, try 4', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6500270000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Verve',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Discover Card. try 5', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6221260000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Discover Card',
|
||||||
|
icon: 'Discover-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Discover Card. try 6', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6229250000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Discover Card',
|
||||||
|
icon: 'Discover-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Maestro, try 8', () => {
|
||||||
|
const cardDetails = utils.identifyCard('6666660000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '6*** **** **** 0000',
|
||||||
|
type: 'Maestro',
|
||||||
|
icon: 'MAESTRO.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Petroleum / Other Card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('7000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '7*** **** **** 0000',
|
||||||
|
type: 'Petroleum / Other Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns Health / Telco / Other Card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('8000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '8*** **** **** 0000',
|
||||||
|
type: 'Health / Telco / Other Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns National / Other Card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('9000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: '9*** **** **** 0000',
|
||||||
|
type: 'National / Other Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns error invalid card', () => {
|
||||||
|
const cardDetails = utils.identifyCard('a000000000000000');
|
||||||
|
return expect(cardDetails).to.deep.equal({
|
||||||
|
hiddenString: 'a*** **** **** 0000',
|
||||||
|
type: 'Invalid Card',
|
||||||
|
icon: 'Generic-card.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
577
node_server/ComServe/specs/valid.spec.js
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
/* globals describe, beforeEach, afterEach, it */
|
||||||
|
/**
|
||||||
|
* Unit testing file for the validation code
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const testGlobals = require('../../tools/test/testGlobals.js');
|
||||||
|
const chai = require('chai');
|
||||||
|
const valid = require('../valid.js');
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Array of test cases for testing MerchantInvoice validation
|
||||||
|
// Note that all we need is the RequestAmount and the MerchantInvoice itself as
|
||||||
|
// this is NOT validating the whole RedeemPaycode (or other function) details.
|
||||||
|
// See http://10.0.10.242/T1235#30055 for the rules
|
||||||
|
//
|
||||||
|
const merchantInvoiceBasic = [
|
||||||
|
{
|
||||||
|
name: 'no merchant invoice',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tests where Items used for MerchantInvoice are on a NET + VAT basis
|
||||||
|
//
|
||||||
|
const merchantInvoiceNet = [
|
||||||
|
{
|
||||||
|
name: 'rounding down line',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 2,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 2, // 2p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, // 2p * 20% = 0.4p = round down to 0
|
||||||
|
Line_TotalAmount: 2 // Net + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rounding up line',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 4,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 3, // 3p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 1, // 3p * 20% = 0.6p = round up to 1
|
||||||
|
Line_TotalAmount: 4 // Net + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round down, but round up in total',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 5,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 2, // 2p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 1, // 2p * 2 * 20% = 0.8p = round up to 1
|
||||||
|
Line_TotalAmount: 5 // Net * 2 + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round up, but round down in total',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 7,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 3, // 3p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 1, // 3p * 2 * 20% = 1.2p = round down to 1
|
||||||
|
Line_TotalAmount: 7 // Net * 2 + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cases that should fail
|
||||||
|
//
|
||||||
|
{
|
||||||
|
name: 'line INCORRECTLY rounded up',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 3,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 2, // 2p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 1, // should be 2p * 20% = 0.4p = round down to 0
|
||||||
|
Line_TotalAmount: 3 // Net + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'line INCORRECTLY rounded down',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 3,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 3, // 3p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, // Should be 3p * 20% = 0.6p = round up to 1
|
||||||
|
Line_TotalAmount: 3 // Net + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round down but round up in total, being INCORRECTLY rounded down',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 4,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 2, // 2p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 0, // should be 2p * 2 * 20% = 0.8p = round up to 1
|
||||||
|
Line_TotalAmount: 4 // Net * 2 + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round up but round down in total, being INCORRECTLY rounded up',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 6,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 3, // 3p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 0, // should be 3p * 2 * 20% = 1.2p = round down to 1
|
||||||
|
Line_TotalAmount: 6 // Net * 2 + VAT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tests where Items used for MerchantInvoice are on a GROSS basis
|
||||||
|
//
|
||||||
|
const merchantInvoiceGross = [
|
||||||
|
{
|
||||||
|
name: 'rounding down line',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 2,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 2, // 2p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, // 2p - (2p / 120%) = (2p - 1.666...) = 0.22... = round down to 0
|
||||||
|
Line_TotalAmount: 2 // Gross * count
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rounding up line',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 3,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 3, // 3p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 1, // 3p - (3p/120%) = (3p - 2.5p) = 0.5p = round up to 1
|
||||||
|
Line_TotalAmount: 3 // Gross * count
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round down, but round up in total',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 4,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 2, // 2p
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 1, // 4p - (4p/120%) = (4p - 3.33) = 0.66..p = round up to 1
|
||||||
|
Line_TotalAmount: 4 // Gross * 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round up, but are exact pennies in total',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 6,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 3, // 3p
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 1, // 6p - (6p/120%) = (6 - 5) = 1p exactly
|
||||||
|
Line_TotalAmount: 6 // Gross * 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round up, but round down in total',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 8,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 4, // 4p
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 1, // 8p - (8p/120%) = (8 - 6.66) = 1.333 = round down
|
||||||
|
Line_TotalAmount: 8 // Gross * 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cases that should fail
|
||||||
|
//
|
||||||
|
{
|
||||||
|
name: 'INCORRECTLY rounding up line',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 3,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 2, // 2p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 1, // should be 2p - (2p / 120%) = (2p - 1.666...) = 0.22... = round down to 0
|
||||||
|
Line_TotalAmount: 2 // Gross * count
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'INCORRECTLY rounding down line',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 3,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 3, // 3p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, // should be 3p - (3p/120%) = (3p - 2.5p) = 0.5p = round up to 1
|
||||||
|
Line_TotalAmount: 3 // Gross * count
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round down but round up in total, being INCORRECTLY rounded down',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 4,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 2, // 2p
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 0, // should be 4p - (4p/120%) = (4p - 3.33) = 0.66..p = round up to 1
|
||||||
|
Line_TotalAmount: 4 // Gross * 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round up but are exact pennies in total, being INCORRECTLY rounded up',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 6,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 3, // 3p
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 2, // should be 6p - (6p/120%) = (6 - 5) = 1p exactly
|
||||||
|
Line_TotalAmount: 6 // Gross * 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2 items that would individually round up but round down in total, being INCORRECTLY rounded up',
|
||||||
|
valid: false,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 8,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 4, // 4p
|
||||||
|
Item_Quantity: 2,
|
||||||
|
Line_VATAmount: 2, // should be 8p - (8p/120%) = (8 - 6.66) = 1.333 = round down
|
||||||
|
Line_TotalAmount: 8 // Gross * 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests of mixed types of line items in a single transaction
|
||||||
|
*/
|
||||||
|
const merchantInvoiceMixed = [
|
||||||
|
{
|
||||||
|
name: 'one round up, one down, for each of NET and GROSS + one 0% vat',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 21,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 2, // 2p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, // 2p - (2p / 120%) = (2p - 1.666...) = 0.22... = round down to 0
|
||||||
|
Line_TotalAmount: 2 // Gross * count
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 3, // 3p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 1, // 3p - (3p/120%) = (3p - 2.5p) = 0.5p = round up to 1
|
||||||
|
Line_TotalAmount: 3 // Gross * count
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 2, // 2p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, // 2p * 20% = 0.4p = round down to 0
|
||||||
|
Line_TotalAmount: 2 // Net + VAT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Item_VATRate: 2000, // 20%
|
||||||
|
Item_NetAmount: 3, // 3p
|
||||||
|
Item_GrossAmount: null, // using Net
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 1, // 3p * 20% = 0.6p = round up to 1
|
||||||
|
Line_TotalAmount: 4 // Net + VAT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Item_VATRate: 0, // 0%
|
||||||
|
Item_NetAmount: null, // using Gross
|
||||||
|
Item_GrossAmount: 10, // 10p
|
||||||
|
Item_Quantity: 1,
|
||||||
|
Line_VATAmount: 0, //
|
||||||
|
Line_TotalAmount: 10 // Gross * count
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests of mixed types of line items in a single transaction
|
||||||
|
*/
|
||||||
|
const merchantInvoiceFracQuantity = [
|
||||||
|
{
|
||||||
|
name: 'Line_TotalAmount rounded to nearest (gross)',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 1,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 0, // 0%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 1, // 1p
|
||||||
|
Item_Quantity: 0.5,
|
||||||
|
Line_VATAmount: 0, // 0
|
||||||
|
Line_TotalAmount: 1 // Gross * count = 0.5 = round up to 1p
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Item_VATRate: 0, // 0%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 1, // 1p
|
||||||
|
Item_Quantity: 0.49,
|
||||||
|
Line_VATAmount: 0, // 0
|
||||||
|
Line_TotalAmount: 0 // Gross * count = 0.49 = round down to 0p
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Line_TotalAmount rounded to nearest (net)',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 1,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 0, // 0%
|
||||||
|
Item_NetAmount: 1, // 1p
|
||||||
|
Item_GrossAmount: null, // using net
|
||||||
|
Item_Quantity: 0.5,
|
||||||
|
Line_VATAmount: 0, // 0
|
||||||
|
Line_TotalAmount: 1 // Gross * count = 0.5 = round up to 1p
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Item_VATRate: 0, // 0%
|
||||||
|
Item_NetAmount: 1, // 1p
|
||||||
|
Item_GrossAmount: null, // using net
|
||||||
|
Item_Quantity: 0.49,
|
||||||
|
Line_VATAmount: 0, // 0
|
||||||
|
Line_TotalAmount: 0 // Gross * count = 0.49 = round down to 0p
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Line_VATAmount based on post-rounded numbers, not pre-rounded (net)',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 3,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 3000, // 30%
|
||||||
|
Item_NetAmount: 3, // 3p
|
||||||
|
Item_GrossAmount: null, // using net
|
||||||
|
Item_Quantity: 0.5,
|
||||||
|
Line_VATAmount: 1, // 2p * 0.3 = 0.6 => 1p rounded up. Not 0p if 1.5p used
|
||||||
|
Line_TotalAmount: 3 // Net * count = 1.5 = round up to 2p. Plus VAT above.
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Line_VATAmount based on post-rounded numbers, not pre-rounded (gross)',
|
||||||
|
valid: true,
|
||||||
|
data: {
|
||||||
|
RequestAmount: 2,
|
||||||
|
MerchantInvoice: [
|
||||||
|
{
|
||||||
|
Item_VATRate: 4000, // 40%
|
||||||
|
Item_NetAmount: null, // using gross
|
||||||
|
Item_GrossAmount: 3, // 3p
|
||||||
|
Item_Quantity: 0.5,
|
||||||
|
Line_VATAmount: 1, // 2p - (2p / 1.4) = 2-1.43 = 0.57 => 0p rounded down. Not 1p if 1.5p used
|
||||||
|
Line_TotalAmount: 2 // Gross * count = 1.5 = round up to 2p.
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categories of test cases
|
||||||
|
*/
|
||||||
|
const groups = [
|
||||||
|
{
|
||||||
|
description: 'general transactions',
|
||||||
|
cases: merchantInvoiceBasic
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'transaction with merchant invoice with NET item(s)',
|
||||||
|
cases: merchantInvoiceNet
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'transaction with merchant invoice with GROSS item(s)',
|
||||||
|
cases: merchantInvoiceGross
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'transaction with merchant invoice with MIXED item(s)',
|
||||||
|
cases: merchantInvoiceMixed
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'transaction with fractional quantity of items',
|
||||||
|
cases: merchantInvoiceFracQuantity
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the test cases array, creating a test case for each one.
|
||||||
|
*
|
||||||
|
* @param {Object[]} cases - array of test cases
|
||||||
|
*/
|
||||||
|
function applyTestCases(cases) {
|
||||||
|
const validate = valid.validateRedeemPayCode;
|
||||||
|
|
||||||
|
for (let i = 0; i < cases.length; ++i) {
|
||||||
|
/**
|
||||||
|
* Get the testcase, then the test data in the correct format.
|
||||||
|
*/
|
||||||
|
const tc = cases[i];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a meaningful name for the test.
|
||||||
|
*/
|
||||||
|
let name = tc.valid ? 'should accept ' : 'should NOT accept ';
|
||||||
|
|
||||||
|
name += tc.name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a test for this case.
|
||||||
|
*/
|
||||||
|
it(name, () => {
|
||||||
|
const expected = expect(validate(tc.data));
|
||||||
|
|
||||||
|
if (tc.valid) {
|
||||||
|
return expected.to.equal(null);
|
||||||
|
} else {
|
||||||
|
return expected.to.include({code: 174});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test definitions
|
||||||
|
*/
|
||||||
|
describe('validation', () => {
|
||||||
|
describe('validateRedeemPaycode', () => {
|
||||||
|
//
|
||||||
|
// Loop through all the groups of tests cases, adding a describe for each one
|
||||||
|
//
|
||||||
|
for (let i = 0; i < groups.length; ++i) {
|
||||||
|
describe(groups[i].description, () => {
|
||||||
|
applyTestCases(groups[i].cases);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
1090
node_server/ComServe/utils.js
Normal file
335
node_server/ComServe/valid.js
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Comcarde Node.js General Functionality
|
||||||
|
// Provides -Bridge- pay functionality.
|
||||||
|
// Copyright 2015 Comcarde
|
||||||
|
// Written by Keith Symington and Richard Vanneck
|
||||||
|
// Refactored 17-9-2015 by KJS
|
||||||
|
// Largely replaced by JSON Schema validators Oct 2016 by RJT
|
||||||
|
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Includes
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const utils = require(global.pathPrefix + 'utils.js');
|
||||||
|
const config = require(global.configFile);
|
||||||
|
const debug = require('debug')('validation:invoices');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare the exports at the top so we know what functions are used externally
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
checkInt,
|
||||||
|
checkDP,
|
||||||
|
validateFieldTimeStamp,
|
||||||
|
validateFieldHMAC,
|
||||||
|
validateRedeemPayCode,
|
||||||
|
validateFieldMerchantInvoice
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks over a string for a whitelisted set of characters.
|
||||||
|
*
|
||||||
|
* @type {Function} checkWhitelist
|
||||||
|
* @param {!string} inputString - The string to be checked for valid characters.
|
||||||
|
* @param {!string} whitelist - The list of valid charactes as a string 'abcde...'. Also accepts RegEx 'a-z'.
|
||||||
|
* @param {!int} offset - Added to the index error position: for reporting only. The default should be 0.
|
||||||
|
* @returns {null | string} Returns either null for success or an error string if there was a problem.
|
||||||
|
*/
|
||||||
|
function checkWhitelist(inputString, whitelist, offset) {
|
||||||
|
const regexString = '[^' + whitelist + ']'; // Wrap the whitelist with not operator, and repeat 0-many times.
|
||||||
|
const regex = new RegExp(regexString); // No need for global - first invalid char is fine.
|
||||||
|
const index = regex.exec(inputString);
|
||||||
|
if (index) {
|
||||||
|
return ('Error at index [' + (index.index + offset) + '], [' + index[0] + '].');
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a variable is a safe integer or not.
|
||||||
|
* Used externally by utils.js to check the config values loaded from the data.
|
||||||
|
*
|
||||||
|
* @param {any} input - the item to be tested
|
||||||
|
*
|
||||||
|
* @returns {string | null} - 'null' to confirm it is an Integer or a string if it is not.
|
||||||
|
*/
|
||||||
|
function checkInt(input) {
|
||||||
|
if (_.isSafeInteger(input)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return 'Not a number.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a variable is in ISO date format (zulu).
|
||||||
|
* input must be length checked before passing at 24 chars.
|
||||||
|
* Note that this function seems to be excessive versus a RegEx but we have had problems
|
||||||
|
* with the results so it was taken back to basics so that the code is reliable if ported.
|
||||||
|
*
|
||||||
|
* @param {string} input - the string to test
|
||||||
|
* @returns {string | null} - Returns 'null' to confirm it is valid or a string if it is not.
|
||||||
|
*/
|
||||||
|
/* eslint-disable complexity */
|
||||||
|
function checkDateTime(input) {
|
||||||
|
// Legacy code so cyclomatic comlexity is high.
|
||||||
|
// Local variables.
|
||||||
|
let output;
|
||||||
|
|
||||||
|
// Check items.
|
||||||
|
output = checkWhitelist(input.substr(0, 4), utils.numeric, 0);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid year. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(4) !== '-') {
|
||||||
|
return ('Invalid separator. Error at index [4].');
|
||||||
|
}
|
||||||
|
output = checkWhitelist(input.substr(5, 2), utils.numeric, 5);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid month. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(7) !== '-') {
|
||||||
|
return ('Invalid separator. Error at index [7].');
|
||||||
|
}
|
||||||
|
output = checkWhitelist(input.substr(8, 2), utils.numeric, 8);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid day. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(10) !== 'T') {
|
||||||
|
return ('Invalid time indicator. Error at index [10].');
|
||||||
|
}
|
||||||
|
output = checkWhitelist(input.substr(11, 2), utils.numeric, 11);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid hours. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(13) !== ':') {
|
||||||
|
return ('Invalid separator. Error at index [13].');
|
||||||
|
}
|
||||||
|
output = checkWhitelist(input.substr(14, 2), utils.numeric, 14);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid minutes. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(16) !== ':') {
|
||||||
|
return ('Invalid separator. Error at index [16].');
|
||||||
|
}
|
||||||
|
output = checkWhitelist(input.substr(17, 2), utils.numeric, 17);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid seconds. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(19) !== '.') {
|
||||||
|
return ('Invalid separator. Error at index [19].');
|
||||||
|
}
|
||||||
|
output = checkWhitelist(input.substr(20, 3), utils.numeric, 20);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid milliseconds. ' + output);
|
||||||
|
}
|
||||||
|
if (input.charAt(23) !== 'Z') {
|
||||||
|
return ('Invalid time zone. Error at index [23].');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/* eslint-enable complexity */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the number of dp after the decimal point and returns this number.
|
||||||
|
* Used by mainDB.js to validate the number of decimal places in coordinates
|
||||||
|
*
|
||||||
|
* @type {Function} checkDP
|
||||||
|
* @param {!number} numberToCheck - The number to process.
|
||||||
|
*
|
||||||
|
* @returns {number} The number of decimal places
|
||||||
|
*/
|
||||||
|
function checkDP(numberToCheck) {
|
||||||
|
const tempString = String(numberToCheck);
|
||||||
|
if (0 > tempString.indexOf('.')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const pieces = tempString.split('.');
|
||||||
|
return pieces[1].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Common field validations.
|
||||||
|
// All return null for success or a string otherwise.
|
||||||
|
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the provided HMAC is in the correct format.
|
||||||
|
* Used externally by auth.js to validate bridge-hmac
|
||||||
|
*
|
||||||
|
* @param {string} input - the string to test
|
||||||
|
* @returns {string | null} - error string on error, or null on success
|
||||||
|
*/
|
||||||
|
function validateFieldHMAC(input) {
|
||||||
|
if (!_.isString(input)) {
|
||||||
|
return 'Identifier not a string.';
|
||||||
|
}
|
||||||
|
if (input.length !== (config.HMACBytes * 2)) {
|
||||||
|
return ('Identifier length not ' + (config.HMACBytes * 2) + '.');
|
||||||
|
}
|
||||||
|
const output = checkWhitelist(input, utils.lowerCaseHex, 0);
|
||||||
|
if (output) {
|
||||||
|
return ('Invalid characters in HMAC. ' + output);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that timestamps are correct.
|
||||||
|
* Used externally by auth.js to validate bridge-timestamp
|
||||||
|
*
|
||||||
|
* @param {any} input - the item to validate
|
||||||
|
*
|
||||||
|
* @returns {string | null} A string detailing any error, or null if no errors found.
|
||||||
|
*/
|
||||||
|
function validateFieldTimeStamp(input) {
|
||||||
|
if (!_.isString(input)) {
|
||||||
|
return 'TimeStamp not a string.';
|
||||||
|
}
|
||||||
|
if (input.length !== 24) {
|
||||||
|
return 'TimeStamp should be 24 characters long.';
|
||||||
|
}
|
||||||
|
const output = checkDateTime(input);
|
||||||
|
if (output) {
|
||||||
|
return ('TimeStamp not in ISO date format. ' + output);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the functional correctness of a MerchantInvoice:
|
||||||
|
* - No rows with the same Item_ID
|
||||||
|
* - Check that the Net, VAT, Quantity, and Total match up in a row
|
||||||
|
* - Check that the rows total the requested overall TotalAmount
|
||||||
|
*
|
||||||
|
* @param {Object[]} input - array of merchantInvoice line items
|
||||||
|
* @param {number} expectedTotal - the overall total the line items should match
|
||||||
|
* @param {boolean} allowRepeatItems - true to allow the same Item_ID to appear multiple times
|
||||||
|
*
|
||||||
|
* @returns {string | null} - an error string if any errors are found, or null if no errors
|
||||||
|
*/
|
||||||
|
function validateFieldMerchantInvoice(input, expectedTotal, allowRepeatItems) {
|
||||||
|
const items = {};
|
||||||
|
let cumulativeTotal = 0;
|
||||||
|
|
||||||
|
// Iterate all line items.
|
||||||
|
for (let counter = 0; counter < input.length; counter++) {
|
||||||
|
const line = input[counter];
|
||||||
|
if (line.Item_ID && !allowRepeatItems) {
|
||||||
|
/**
|
||||||
|
* Check for duplicate Item_IDs.
|
||||||
|
*/
|
||||||
|
if (items.hasOwnProperty(line.Item_ID)) {
|
||||||
|
return 'Invalid body.MerchantInvoice[' + counter + ']: ' +
|
||||||
|
'Duplicate Item_ID in MerchantInvoice.';
|
||||||
|
} else {
|
||||||
|
items[line.Item_ID] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the calculated per-line vat and total amounts are correct,
|
||||||
|
* depending on whether the base price is on a Net or Gross basis.
|
||||||
|
*/
|
||||||
|
let expectedLineTotal = -1;
|
||||||
|
let expectedVatTotal = -1;
|
||||||
|
if (_.isInteger(line.Item_NetAmount) && _.isNull(line.Item_GrossAmount)) {
|
||||||
|
/**
|
||||||
|
* This is a NET line item
|
||||||
|
*/
|
||||||
|
const netTotal = _.round(line.Item_NetAmount * line.Item_Quantity);
|
||||||
|
const vatMultiplier = line.Item_VATRate / (100 * 100);
|
||||||
|
expectedVatTotal = _.round(netTotal * vatMultiplier);
|
||||||
|
expectedLineTotal = netTotal + expectedVatTotal;
|
||||||
|
debug(
|
||||||
|
'NET:',
|
||||||
|
line.Item_NetAmount, line.Item_Quantity, line.Item_VATRate
|
||||||
|
);
|
||||||
|
debug(
|
||||||
|
'Interim:',
|
||||||
|
netTotal, vatMultiplier
|
||||||
|
);
|
||||||
|
} else if (_.isNull(line.Item_NetAmount) && _.isInteger(line.Item_GrossAmount)) {
|
||||||
|
/**
|
||||||
|
* This is a GROSS line item
|
||||||
|
*/
|
||||||
|
expectedLineTotal = _.round(line.Item_GrossAmount * line.Item_Quantity);
|
||||||
|
const vatMultiplier = line.Item_VATRate / (100 * 100);
|
||||||
|
expectedVatTotal = _.round(expectedLineTotal - (expectedLineTotal / (1 + vatMultiplier)));
|
||||||
|
|
||||||
|
debug(
|
||||||
|
'GROSS:',
|
||||||
|
line.Item_GrossAmount, line.Item_Quantity, line.Item_VATRate
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Both are set (or both null) which is wrong
|
||||||
|
*/
|
||||||
|
return 'Invalid body.MerchantInvoice[' + counter + ']: ' +
|
||||||
|
'Only 1 of NetAmount and GrossAmount should be set (with the other null)';
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(
|
||||||
|
'-- Actual: ',
|
||||||
|
line.Line_VATAmount, line.Line_TotalAmount
|
||||||
|
);
|
||||||
|
debug(
|
||||||
|
'-- Expect: ',
|
||||||
|
expectedVatTotal, expectedLineTotal
|
||||||
|
);
|
||||||
|
|
||||||
|
if (expectedVatTotal !== line.Line_VATAmount) {
|
||||||
|
return 'Invalid body.MerchantInvoice[' + counter + ']: ' +
|
||||||
|
'Line_VATAmount incorrect.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedLineTotal !== line.Line_TotalAmount) {
|
||||||
|
return 'Invalid body.MerchantInvoice[' + counter + ']: ' +
|
||||||
|
'Line_TotalAmount incorrect.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record total.
|
||||||
|
cumulativeTotal += line.Line_TotalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check total.
|
||||||
|
if (expectedTotal !== cumulativeTotal) {
|
||||||
|
return 'Invalid body.MerchantInvoice: Cumulative total does not match RequestAmount.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides additional function validation of the RedeemPaycode command. In
|
||||||
|
* particular, it validates that the MerchantInvoice values are consistent,
|
||||||
|
* both per-row and compared to the overall total.
|
||||||
|
*
|
||||||
|
* @param {Object} inputInfo - the input to be functionally validated
|
||||||
|
*
|
||||||
|
* @returns {Object|null} - An object to return to the user on error, or null if no errors found
|
||||||
|
*/
|
||||||
|
function validateRedeemPayCode(inputInfo) {
|
||||||
|
// Key and token check.
|
||||||
|
const errorcode = 174;
|
||||||
|
let output;
|
||||||
|
|
||||||
|
if ('MerchantInvoice' in inputInfo) {
|
||||||
|
output = validateFieldMerchantInvoice(
|
||||||
|
inputInfo.MerchantInvoice,
|
||||||
|
inputInfo.RequestAmount,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
if (output) {
|
||||||
|
debug('--- Error:', output);
|
||||||
|
return utils.createError(errorcode, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All valid.
|
||||||
|
return null;
|
||||||
|
}
|
170
node_server/ComServe/worldpay.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/**
|
||||||
|
* @fileOverview Node.js Worldpay Acquiring Code for Bridge Pay
|
||||||
|
* @preserve Copyright 2016 Comcarde Ltd.
|
||||||
|
* @author Keith Symington
|
||||||
|
* @see #bridge_server-core
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes needed for this module.
|
||||||
|
*/
|
||||||
|
var request = require('request');
|
||||||
|
var log = require(global.pathPrefix + 'log.js');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var config = require(global.configFile);
|
||||||
|
var sms = require(global.pathPrefix + 'sms.js');
|
||||||
|
|
||||||
|
exports.useMCC6012 = 0; // Add MCC information where appropriate.
|
||||||
|
exports.useAVS = 1; // Use AVS on tokenisation.
|
||||||
|
exports.worldpayPostData = 1; // Shows the info sent to Worldpay.
|
||||||
|
exports.primaryFailedComms = 0; // Ticks up every time communications fail with Worldpay's primary server.
|
||||||
|
exports.worldpaySMSAlertSent = 0; // Designed to prevent multiple SMS calls.
|
||||||
|
exports.worldpayTimeout = 25000; // Apps use 30 seconds as we need a little time to respond to them.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worldpay API function call.
|
||||||
|
*
|
||||||
|
* @type {function} worldpayFunction
|
||||||
|
* @param {String} method - http method: 'GET', 'POST', etc.
|
||||||
|
* @param {?string} urlPath - Additional path information - e.g. tokens, order etc. Alternatively use null.
|
||||||
|
* @param {?string} authKey - Add the key string here if a key needs to be used. Alternatively use null.
|
||||||
|
* @param {?object} additionalHeaders - JSON object with any additional headers. null if none needed.
|
||||||
|
* @param {!object} postBody - JSON body for the request data in the main packet. null if none needed.
|
||||||
|
* @param {!function} callback - Function to call when processing complete..
|
||||||
|
*/
|
||||||
|
exports.worldpayFunction = function(method, urlPath, authKey, additionalHeaders, postBody, callback) {
|
||||||
|
/**
|
||||||
|
* Set the default headers.
|
||||||
|
*/
|
||||||
|
var postHeaders = {};
|
||||||
|
_.merge(postHeaders, {'User-Agent': 'Super Agent/0.0.1', 'Content-type': 'application/json'});
|
||||||
|
/**
|
||||||
|
* Add the auth key if it exists.
|
||||||
|
*/
|
||||||
|
if (authKey !== null) {
|
||||||
|
postHeaders.Authorization = authKey;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add any additional headers.
|
||||||
|
*/
|
||||||
|
if (additionalHeaders !== null) {
|
||||||
|
_.merge(postHeaders, additionalHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the data to be posted. Never post this information on the live server.
|
||||||
|
if (exports.worldpayPostData && config.isDevEnv) {
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('[OUT] headers: ' + JSON.stringify(postHeaders) + ' body: ' + JSON.stringify(postBody)),
|
||||||
|
'worldpay.worldpayFunction',
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the data by submitting it to Worldpay.
|
||||||
|
* The assumption is that there must be data in the body to proceed.
|
||||||
|
*/
|
||||||
|
if (postBody) {
|
||||||
|
/**
|
||||||
|
* Configure the request.
|
||||||
|
*/
|
||||||
|
var location = config.worldpayPrimaryGateway;
|
||||||
|
if (urlPath !== null) {
|
||||||
|
location += urlPath;
|
||||||
|
}
|
||||||
|
var options = {
|
||||||
|
url: location,
|
||||||
|
method: method,
|
||||||
|
headers: postHeaders,
|
||||||
|
json: true,
|
||||||
|
strictSSL: true,
|
||||||
|
body: postBody,
|
||||||
|
timeout: exports.worldpayTimeout
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the request.
|
||||||
|
*/
|
||||||
|
request(options, function(error, response, body) {
|
||||||
|
/**
|
||||||
|
* Handle the response appropriately.
|
||||||
|
*/
|
||||||
|
var worldpayResult = '';
|
||||||
|
if (!error && (response.statusCode === 200)) {
|
||||||
|
/**
|
||||||
|
* A response was received. 200 means the result was successful.
|
||||||
|
*/
|
||||||
|
worldpayResult = body;
|
||||||
|
return callback(null, worldpayResult);
|
||||||
|
} else if (!error && (response.statusCode !== 200)) {
|
||||||
|
/**
|
||||||
|
* A response was received. It's not 200 so it's an error.
|
||||||
|
* The important values are:
|
||||||
|
* https://developer.worldpay.com/jsonapi/api#errors
|
||||||
|
*/
|
||||||
|
worldpayResult = body;
|
||||||
|
return callback(worldpayResult);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Return a request error.
|
||||||
|
*/
|
||||||
|
return callback('Error: ' + error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return callback('No data to process.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function deals with Worldpay communication failures. Currently there is no switchover gateway.
|
||||||
|
*
|
||||||
|
* @type {function} commsFailure
|
||||||
|
* @param {!string} source - The function where the error was called from e.g. 'AddCard.process'.
|
||||||
|
*/
|
||||||
|
exports.commsFailure = function(source) {
|
||||||
|
/**
|
||||||
|
* General error - usually indicates Worldpay is down.
|
||||||
|
*/
|
||||||
|
if (exports.primaryFailedComms >= (config.worldpayNotificationThreshold - 1)) {
|
||||||
|
/**
|
||||||
|
* Inform admins as it is over the threshold.
|
||||||
|
*/
|
||||||
|
if (exports.worldpaySMSAlertSent === 0) {
|
||||||
|
/**
|
||||||
|
* Block multiple SMS messages and send a single one to the admin(s).
|
||||||
|
* Note that SMS is blocked before we know it has been sent; the callback structure means that this could
|
||||||
|
* be initialised hundreds of times on a loaded system before the first one returned.
|
||||||
|
*/
|
||||||
|
exports.worldpaySMSAlertSent = 1;
|
||||||
|
sms.sendSMS(null, (sms.adminMobile + ',' + sms.backupMobile),
|
||||||
|
config.worldpayPrimaryGatewayFailure, function(err, smsBalance) {
|
||||||
|
if (err) {
|
||||||
|
log.system(
|
||||||
|
'ERROR',
|
||||||
|
'Unable to send SMS.',
|
||||||
|
source,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success.
|
||||||
|
*/
|
||||||
|
log.system(
|
||||||
|
'INFO',
|
||||||
|
('Worldpay primary gateway failure. SMS sent to admins (SMS balance now ' + smsBalance + ').'),
|
||||||
|
source,
|
||||||
|
'',
|
||||||
|
'System',
|
||||||
|
'127.0.0.1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exports.primaryFailedComms += 1;
|
||||||
|
}
|
||||||
|
};
|
BIN
node_server/WebApp/defaultCompanyLogo0.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
node_server/WebApp/defaultSelfie.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
node_server/WebApp/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
node_server/WebApp/icons/AMEX.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
node_server/WebApp/icons/BRIDGE_MERCHANT.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
node_server/WebApp/icons/CARTEBLEUE.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
node_server/WebApp/icons/DINERS.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
node_server/WebApp/icons/Dankort.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
node_server/WebApp/icons/Diners-Generic.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
node_server/WebApp/icons/Discover-card.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
node_server/WebApp/icons/Electron.png
Normal file
After Width: | Height: | Size: 6.0 KiB |