diff --git a/.gitignore b/.gitignore index 92dc1c67d..f700d7475 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# Created by .ignore support plugin (hsz.mobi) -### Node template # Logs logs *.log @@ -19,4 +17,9 @@ node_modules/ # IntelliJ .idea -*.iml \ No newline at end of file +*.iml + +secrets.yaml + +# NYC test coverage +.nyc_output/ diff --git a/.travis.yml b/.travis.yml index ad8345c7a..fabdb127a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,29 @@ language: node_js node_js: - - '10' + - '8' env: - TEST_DIR=notification - TEST_DIR=extension before_install: - - npm i -g npm@^6.9.0 + - npm i -g npm@^6.4.1 +services: + - docker script: - cd $TEST_DIR - npm ci - npm run test - if [ "$TEST_DIR" = "extension" ]; then npm run cypress; fi + - cd $TRAVIS_BUILD_DIR + +before_deploy: + - docker login -u="${DOCKER_USERNAME}" -p="${DOCKER_PASSWORD}" + +after_deploy: + - docker logout + +deploy: + - provider: script + script: bash ./travis-build.sh "$TEST_DIR" + on: + tags: true + condition: $TRAVIS_TAG =~ ^v[0-9]+.[0-9]+.[0-9]+ \ No newline at end of file diff --git a/extension/.dockerignore b/extension/.dockerignore new file mode 100644 index 000000000..f77cb9dd1 --- /dev/null +++ b/extension/.dockerignore @@ -0,0 +1,7 @@ +.gitignore +.git +test +cypress +cypress. +node_modules +json \ No newline at end of file diff --git a/extension/Dockerfile b/extension/Dockerfile new file mode 100644 index 000000000..f7fad13ba --- /dev/null +++ b/extension/Dockerfile @@ -0,0 +1,13 @@ +FROM mhart/alpine-node:8 +MAINTAINER Professional Services + +WORKDIR /app + +RUN apk --update add make python + +RUN apk --update add git + +COPY . /app + +RUN npm ci --only=prod +ENTRYPOINT ["npm", "run", "start"] \ No newline at end of file diff --git a/extension/package.json b/extension/package.json index 4cbe259ef..d691f7ab0 100644 --- a/extension/package.json +++ b/extension/package.json @@ -5,7 +5,7 @@ "license": "MIT", "scripts": { "test": "mocha --exit --timeout 30000 --full-trace test/**/*.spec.js", - "start": "node ./src/index.js", + "start": "node ./src/init.js", "lint": "eslint .", "cypress:run": "cypress run", "cypress:open": "cypress open", diff --git a/extension/resources/payment-interface-interaction-types.json b/extension/resources/payment-interface-interaction-types.json index 56e91ef2f..58fff7390 100644 --- a/extension/resources/payment-interface-interaction-types.json +++ b/extension/resources/payment-interface-interaction-types.json @@ -9,13 +9,13 @@ ], "fieldDefinitions": [ { - "name": "timestamp", + "name": "createdAt", "label": { - "en": "timestamp" + "en": "createdAt" }, "required": true, "type": { - "name": "String" + "name": "DateTime" }, "inputHint": "SingleLine" }, @@ -76,4 +76,4 @@ } ] } -] \ No newline at end of file +] diff --git a/extension/src/config/init/ensure-api-extensions.js b/extension/src/config/init/ensure-api-extensions.js index ec22d36f9..b8f5c9ff4 100644 --- a/extension/src/config/init/ensure-api-extensions.js +++ b/extension/src/config/init/ensure-api-extensions.js @@ -1,15 +1,19 @@ const _ = require('lodash') +const utils = require('../../utils') const apiExtensionTemplate = require('../../../resources/api-extension.json') +const logger = utils.getLogger() async function ensureApiExtensions (ctpClient, ctpAdyenIntegrationBaseUrl) { try { const extensionDraft = _.template(JSON.stringify(apiExtensionTemplate))({ ctpAdyenIntegrationBaseUrl }) const { body } = await ctpClient.fetch(ctpClient.builder.extensions.where(`key="${apiExtensionTemplate.key}"`)) - if (body.results.length === 0) + if (body.results.length === 0) { await ctpClient.create(ctpClient.builder.extensions, extensionDraft) + logger.info('Successfully created api extension') + } } catch (e) { - console.error('Error when creating API extension, skipping...', JSON.stringify(e)) + logger.error(e, 'Error when creating api extension, skipping...') } } diff --git a/extension/src/config/init/ensure-interface-interaction-custom-type.js b/extension/src/config/init/ensure-interface-interaction-custom-type.js index a9e54f59b..7f81c9e8e 100644 --- a/extension/src/config/init/ensure-interface-interaction-custom-type.js +++ b/extension/src/config/init/ensure-interface-interaction-custom-type.js @@ -1,15 +1,20 @@ const Promise = require('bluebird') +const utils = require('../../utils') const interfaceInteractionTypes = require('../../../resources/payment-interface-interaction-types.json') +const logger = utils.getLogger() + async function ensureInterfaceInteractionCustomType (ctpClient) { await Promise.map(interfaceInteractionTypes, async (type) => { try { const { body } = await ctpClient.fetch(ctpClient.builder.types.where(`key="${type.key}"`)) - if (body.results.length === 0) + if (body.results.length === 0) { await ctpClient.create(ctpClient.builder.types, type) + logger.info('Successfully created an interfaceInteraction type') + } } catch (e) { - console.error('Error when creating interface interaction custom type, skipping...', JSON.stringify(e)) + logger.error(e, 'Error when creating interface interaction custom type, skipping...') } }, { concurrency: 3 }) } diff --git a/extension/src/config/init/ensure-payment-custom-type.js b/extension/src/config/init/ensure-payment-custom-type.js index 1d2f69efe..093ecfb6c 100644 --- a/extension/src/config/init/ensure-payment-custom-type.js +++ b/extension/src/config/init/ensure-payment-custom-type.js @@ -1,12 +1,18 @@ const paymentCustomType = require('../../../resources/payment-custom-types.json') +const utils = require('../../utils') + +const logger = utils.getLogger() + async function ensurePaymentCustomType (ctpClient) { try { const { body } = await ctpClient.fetch(ctpClient.builder.types.where(`key="${paymentCustomType.key}"`)) - if (body.results.length === 0) + if (body.results.length === 0) { await ctpClient.create(ctpClient.builder.types, paymentCustomType) + logger.info('Successfully created payment custom type') + } } catch (e) { - console.error('Error when creating payment custom type, skipping...', JSON.stringify(e)) + logger.error(e, 'Error when creating payment custom type, skipping...') } } diff --git a/extension/src/index.js b/extension/src/init.js similarity index 65% rename from extension/src/index.js rename to extension/src/init.js index be1010774..4f5956ea8 100644 --- a/extension/src/index.js +++ b/extension/src/init.js @@ -1,11 +1,12 @@ const init = require('./server.js').setupServer() -require('./config/config') +const utils = require('./utils') const { ensureResources } = require('./config/init/ensure-resources') const port = parseInt(process.env.EXTENSION_PORT || 8080, 10) +const logger = utils.getLogger() init.listen(port, async () => { await ensureResources() - console.log(`Server running at http://127.0.0.1:${port}/`) + logger.info(`Server running at http://127.0.0.1:${port}/`) }) diff --git a/extension/src/paymentHandler/creditCard/credit-card-complete-payment.handler.js b/extension/src/paymentHandler/creditCard/credit-card-complete-payment.handler.js index 201ae57e6..31c763b1c 100644 --- a/extension/src/paymentHandler/creditCard/credit-card-complete-payment.handler.js +++ b/extension/src/paymentHandler/creditCard/credit-card-complete-payment.handler.js @@ -11,7 +11,9 @@ const config = configLoader.load() async function handlePayment (paymentObject) { const validator = _validatePayment(paymentObject) if (validator.hasErrors()) - return validator.buildCtpErrorResponse() + return { + actions: [] + } const { response, request } = await _completePayment(paymentObject) const status = response.status === 200 ? c.SUCCESS : c.FAILURE const responseBody = await response.json() @@ -35,7 +37,6 @@ async function handlePayment (paymentObject) { actions = _.compact(actions) return { - version: paymentObject.version, actions } } diff --git a/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js b/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js index 4cef68174..b3b6bb81a 100644 --- a/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js +++ b/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js @@ -65,7 +65,6 @@ async function handlePayment (paymentObject) { actions = _.compact(actions) return { - version: paymentObject.version, actions } } diff --git a/extension/src/paymentHandler/creditCard/credit-card.handler.js b/extension/src/paymentHandler/creditCard/credit-card.handler.js index a1cd52263..47bd8b27a 100644 --- a/extension/src/paymentHandler/creditCard/credit-card.handler.js +++ b/extension/src/paymentHandler/creditCard/credit-card.handler.js @@ -4,7 +4,6 @@ const pU = require('../payment-utils') const creditCardMakePayment = require('./credit-card-make-payment.handler') const creditCardCompletePayment = require('./credit-card-complete-payment.handler') -const errorMessages = require('../../validator/error-messages') async function handlePayment (paymentObject) { const hasPendingTransaction = _.isObject(pU.getChargeTransactionPending(paymentObject)) @@ -14,10 +13,7 @@ async function handlePayment (paymentObject) { if (hasInitTransaction) return creditCardMakePayment.handlePayment(paymentObject) return { - errors: [{ - code: 'InvalidField', - message: errorMessages.MISSING_TXN_CHARGE_INIT_PENDING - }] + actions: [] } } diff --git a/extension/src/paymentHandler/fetch-payment-methods.handler.js b/extension/src/paymentHandler/fetch-payment-methods.handler.js index c1cffe9de..4305e5cdd 100644 --- a/extension/src/paymentHandler/fetch-payment-methods.handler.js +++ b/extension/src/paymentHandler/fetch-payment-methods.handler.js @@ -8,7 +8,6 @@ async function handlePayment (paymentObject) { const { request, response } = await _fetchPaymentMethods(paymentObject) const responseBody = await response.json() return { - version: paymentObject.version, actions: [ pU.createAddInterfaceInteractionAction({ request, response: responseBody, type: 'getAvailablePaymentMethods' diff --git a/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js b/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js index 74fbbf8bd..ef99b42ec 100644 --- a/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js +++ b/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js @@ -39,7 +39,6 @@ async function handlePayment (paymentObject) { actions = _.compact(actions) return { - version: paymentObject.version, actions } } diff --git a/extension/src/paymentHandler/kcp/kcp-payment.handler.js b/extension/src/paymentHandler/kcp/kcp-payment.handler.js index 1e87f23bd..35ac654e9 100644 --- a/extension/src/paymentHandler/kcp/kcp-payment.handler.js +++ b/extension/src/paymentHandler/kcp/kcp-payment.handler.js @@ -2,17 +2,13 @@ const _ = require('lodash') const pU = require('../payment-utils') const kcpMakePayment = require('./kcp-make-payment.handler') -const errorMessages = require('../../validator/error-messages') async function handlePayment (paymentObject) { const hasInitTransaction = _.isObject(pU.getChargeTransactionInit(paymentObject)) if (hasInitTransaction) return kcpMakePayment.handlePayment(paymentObject) return { - errors: [{ - code: 'InvalidField', - message: errorMessages.MISSING_TXN_CHARGE_INIT_PENDING - }] + actions: [] } } diff --git a/extension/src/paymentHandler/payment-utils.js b/extension/src/paymentHandler/payment-utils.js index d667147ba..80da73774 100644 --- a/extension/src/paymentHandler/payment-utils.js +++ b/extension/src/paymentHandler/payment-utils.js @@ -48,7 +48,7 @@ function createAddInterfaceInteractionAction ( action: 'addInterfaceInteraction', type: { key: c.CTP_INTERFACE_INTERACTION }, fields: { - timestamp: new Date(), + createdAt: new Date(), response: JSON.stringify(response), request: JSON.stringify(request), type, diff --git a/extension/src/paymentHandler/paypal/paypal-complete-payment.handler.js b/extension/src/paymentHandler/paypal/paypal-complete-payment.handler.js index ee89faf8a..162f06fb3 100644 --- a/extension/src/paymentHandler/paypal/paypal-complete-payment.handler.js +++ b/extension/src/paymentHandler/paypal/paypal-complete-payment.handler.js @@ -11,8 +11,9 @@ const config = configLoader.load() async function handlePayment (paymentObject) { const validator = _validatePayment(paymentObject) if (validator.hasErrors()) - return validator.buildCtpErrorResponse() - + return { + actions: [] + } const { response, request } = await _callAdyen(paymentObject) const status = response.status === 200 ? c.SUCCESS : c.FAILURE const responseBody = await response.json() @@ -36,7 +37,6 @@ async function handlePayment (paymentObject) { actions = _.compact(actions) return { - version: paymentObject.version, actions } } diff --git a/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js b/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js index 089e3218d..9f09c26dc 100644 --- a/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js +++ b/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js @@ -39,7 +39,6 @@ async function handlePayment (paymentObject) { actions = _.compact(actions) return { - version: paymentObject.version, actions } } diff --git a/extension/src/paymentHandler/paypal/paypal.handler.js b/extension/src/paymentHandler/paypal/paypal.handler.js index 854c9b483..a8e477dc6 100644 --- a/extension/src/paymentHandler/paypal/paypal.handler.js +++ b/extension/src/paymentHandler/paypal/paypal.handler.js @@ -1,7 +1,6 @@ const _ = require('lodash') const pU = require('../payment-utils') -const errorMessages = require('../../validator/error-messages') const paypalMakePayment = require('./paypal-make-payment.handler') const paypalCompletePayment = require('./paypal-complete-payment.handler') @@ -14,10 +13,7 @@ async function handlePayment (paymentObject) { return paypalMakePayment.handlePayment(paymentObject) return { - errors: [{ - code: 'InvalidField', - message: errorMessages.MISSING_TXN_CHARGE_INIT_PENDING - }] + actions: [] } } diff --git a/extension/src/server.js b/extension/src/server.js index 3f7450470..62828e4aa 100644 --- a/extension/src/server.js +++ b/extension/src/server.js @@ -2,7 +2,6 @@ const http = require('http') const url = require('url') const utils = require('./utils') const { routes: defaultRoutes } = require('./routes') -require('./config/config') const logger = utils.getLogger() diff --git a/extension/src/utils.js b/extension/src/utils.js index 9a930e446..3d2f69500 100644 --- a/extension/src/utils.js +++ b/extension/src/utils.js @@ -1,4 +1,6 @@ const bunyan = require('bunyan') +const configLoader = require('./config/config') +const config = configLoader.load() let logger @@ -23,12 +25,12 @@ function sendResponse ({ response.end(JSON.stringify(data)) } -function getLogger (logLevel) { +function getLogger () { if (!logger) logger = bunyan.createLogger({ name: 'ctp-adyen-integration-extension', stream: process.stderr, - level: logLevel || bunyan.ERROR + level: config.logLevel || bunyan.ERROR }) return logger } diff --git a/extension/test/fixtures/ctp-payment.json b/extension/test/fixtures/ctp-payment.json index 5f1643fd7..8ae8790bd 100644 --- a/extension/test/fixtures/ctp-payment.json +++ b/extension/test/fixtures/ctp-payment.json @@ -66,7 +66,7 @@ }, "fields": { "request": "request", - "timestamp": "2019-02-28T22:16:04.246Z", + "createdAt": "2019-02-28T22:16:04.246Z", "response": "response=", "status": "SUCCESS", "type": "makePayment" @@ -79,11 +79,11 @@ }, "fields": { "request": "request", - "timestamp": "2019-02-28T22:16:19.086Z", + "createdAt": "2019-02-28T22:16:19.086Z", "response": "response", "status": "SUCCESS", "type": "completePayment" } } ] -} \ No newline at end of file +} diff --git a/extension/test/unit/payment.controller.spec.js b/extension/test/unit/payment.controller.spec.js index 38a303d68..151bf351b 100644 --- a/extension/test/unit/payment.controller.spec.js +++ b/extension/test/unit/payment.controller.spec.js @@ -91,13 +91,8 @@ describe('Payment controller', () => { utilsStub.collectRequestData = () => JSON.stringify({ resource: { obj: ctpPaymentClone } }) utilsStub.sendResponse = ({ statusCode, data }) => { - expect(statusCode).to.equal(400) - expect(data).to.deep.equal({ - errors: [{ - code: 'InvalidField', - message: errorMessages.MISSING_PAYLOAD - }] - }) + expect(statusCode).to.equal(200) + expect(data).to.deep.equal({ actions: [] }) } await paymentController.processRequest(mockRequest) @@ -185,23 +180,8 @@ describe('Payment controller', () => { utilsStub.collectRequestData = () => JSON.stringify({ resource: { obj: ctpPaymentClone } }) utilsStub.sendResponse = ({ statusCode, data }) => { - expect(statusCode).to.equal(400) - expect(data).to.deep.equal({ - errors: [ - { - code: 'InvalidField', - message: errorMessages.MISSING_PAYMENT_DATA - }, - { - code: 'InvalidField', - message: errorMessages.MISSING_MD - }, - { - code: 'InvalidField', - message: errorMessages.MISSING_PARES - } - ] - }) + expect(statusCode).to.equal(200) + expect(data).to.deep.equal({ actions: [] }) } await paymentController.processRequest(mockRequest) }) diff --git a/notification/.dockerignore b/notification/.dockerignore new file mode 100644 index 000000000..f77cb9dd1 --- /dev/null +++ b/notification/.dockerignore @@ -0,0 +1,7 @@ +.gitignore +.git +test +cypress +cypress. +node_modules +json \ No newline at end of file diff --git a/notification/Dockerfile b/notification/Dockerfile new file mode 100644 index 000000000..fa487c617 --- /dev/null +++ b/notification/Dockerfile @@ -0,0 +1,13 @@ +FROM mhart/alpine-node:8 +MAINTAINER Professional Services + +WORKDIR /app + +RUN apk --update add make python + +RUN apk --update add git + +COPY . /app + +RUN npm ci --only=prod +ENTRYPOINT ["npm", "run", "start"] diff --git a/notification/package-lock.json b/notification/package-lock.json index f4f077907..a02f83e8a 100644 --- a/notification/package-lock.json +++ b/notification/package-lock.json @@ -268,24 +268,59 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, "array-from": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -409,6 +444,12 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -422,6 +463,12 @@ "which": "^1.2.9" } }, + "damerau-levenshtein": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -446,6 +493,15 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -475,6 +531,46 @@ "nan": "^2.10.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -543,6 +639,170 @@ } } }, + "eslint-config-airbnb": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.0.tgz", + "integrity": "sha512-R9jw28hFfEQnpPau01NO5K/JWMGLi6aymiF6RsnMURjTk+MqZKllCqGK/0tOvHkPi/NWSSOU2Ced/GX++YxLnw==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^13.1.0", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" + } + }, + "eslint-config-airbnb-base": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", + "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", + "dev": true, + "requires": { + "eslint-restricted-globals": "^0.1.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" + } + }, + "eslint-config-commercetools": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-commercetools/-/eslint-config-commercetools-6.0.0.tgz", + "integrity": "sha1-Zx8jpe2LRBOZG46CPunCRCy0xFE=", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz", + "integrity": "sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz", + "integrity": "sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==", + "dev": true, + "requires": { + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.3.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "read-pkg-up": "^2.0.0", + "resolve": "^1.9.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", + "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==", + "dev": true, + "requires": { + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" + } + }, + "eslint-plugin-react": { + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", + "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "resolve": "^1.9.0" + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", @@ -655,6 +915,15 @@ "object-assign": "^4.0.1" } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "flat-cache": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -673,6 +942,12 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -717,17 +992,38 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -823,6 +1119,24 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -835,6 +1149,30 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -869,9 +1207,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -905,6 +1243,15 @@ "diff-match-patch": "^1.0.0" } }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "^3.0.3" + } + }, "just-extend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", @@ -921,6 +1268,28 @@ "type-check": "~0.3.2" } }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", @@ -967,6 +1336,15 @@ "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -1156,6 +1534,18 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "nyc": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", @@ -2196,6 +2586,48 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2233,6 +2665,30 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "parent-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", @@ -2242,6 +2698,21 @@ "callsites": "^3.0.0" } }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2259,6 +2730,12 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -2276,12 +2753,36 @@ } } }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -2300,18 +2801,65 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2431,6 +2979,38 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2456,6 +3036,12 @@ "ansi-regex": "^3.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -2553,6 +3139,16 @@ "punycode": "^2.1.0" } }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/notification/package.json b/notification/package.json index b3f15e47e..29bd0532f 100644 --- a/notification/package.json +++ b/notification/package.json @@ -51,6 +51,11 @@ "mocha": "^5.2.0", "chai": "^4.2.0", "nyc": "^13.3.0", - "ip": "^1.1.5" + "ip": "^1.1.5", + "eslint-config-airbnb": "^17.1.0", + "eslint-config-commercetools": "^6.0.0", + "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-react": "^7.12.4" } } diff --git a/notification/resources/payment-interface-interaction-type.json b/notification/resources/payment-interface-interaction-type.json new file mode 100644 index 000000000..2a4625b2e --- /dev/null +++ b/notification/resources/payment-interface-interaction-type.json @@ -0,0 +1,55 @@ +{ + "key": "ctp-adyen-integration-interaction-notification", + "name": { + "en": "ctp-adyen-integration-interaction-notification" + }, + "resourceTypeIds": [ + "payment-interface-interaction" + ], + "fieldDefinitions": [ + { + "name": "createdAt", + "label": { + "en": "createdAt" + }, + "required": true, + "type": { + "name": "DateTime" + }, + "inputHint": "SingleLine" + }, + { + "name": "transactionId", + "label": { + "en": "transactionId" + }, + "required": false, + "type": { + "name": "String" + }, + "inputHint": "SingleLine" + }, + { + "name": "notification", + "label": { + "en": "notification" + }, + "required": true, + "type": { + "name": "String" + }, + "inputHint": "MultiLine" + }, + { + "name": "status", + "label": { + "en": "status" + }, + "required": false, + "type": { + "name": "String" + }, + "inputHint": "SingleLine" + } + ] +} diff --git a/notification/src/api/notification/notification.controller.js b/notification/src/api/notification/notification.controller.js index 00888564b..7446790a1 100644 --- a/notification/src/api/notification/notification.controller.js +++ b/notification/src/api/notification/notification.controller.js @@ -4,11 +4,14 @@ const ctp = require('../../utils/ctp') const { processNotifications } = require('../../handler/notification/notification.handler') const config = require('../../config/config')() const logger = require('../../utils/logger').getLogger() + const ctpClient = ctp.get(config) // TODO: add JSON schema validation: // https://github.com/commercetools/commercetools-adyen-integration/issues/9 async function handleNotification (request, response) { + if (request.method !== 'POST') + return httpUtils.sendResponse(response) const body = await httpUtils.collectRequestData(request) try { const notifications = _.get(JSON.parse(body), 'notificationItems', []) @@ -17,8 +20,8 @@ async function handleNotification (request, response) { 200, { 'Content-Type': 'application/json' }, JSON.stringify({ notificationResponse: '[accepted]' })) - } catch (err) { - logger.error(err, 'Unexpected exception occurred') + } catch (e) { + logger.error(e, 'Unexpected exception occurred') return httpUtils.sendResponse(response, 500) } } diff --git a/notification/src/config/config.js b/notification/src/config/config.js index ed07714a1..7ef1b7ab5 100644 --- a/notification/src/config/config.js +++ b/notification/src/config/config.js @@ -29,7 +29,7 @@ function getFileConfig () { return fileConfig } -module.exports = function load() { +module.exports = function load () { /** * Load configuration from several sources in this order (last has highest priority): * - default config diff --git a/notification/src/config/init/ensure-interface-interaction-custom-type.js b/notification/src/config/init/ensure-interface-interaction-custom-type.js index 719417cff..e87c4c67b 100644 --- a/notification/src/config/init/ensure-interface-interaction-custom-type.js +++ b/notification/src/config/init/ensure-interface-interaction-custom-type.js @@ -1,17 +1,19 @@ -const Promise = require('bluebird') +const logger = require('../../utils/logger').getLogger() -const interfaceInteractionTypes = require('../../../test/resources/payment-interface-interaction-types.json') +const interfaceInteractionType = require('../../../resources/payment-interface-interaction-type.json') async function ensureInterfaceInteractionCustomType (ctpClient) { - await Promise.map(interfaceInteractionTypes, async (type) => { - try { - const { body } = await ctpClient.fetch(ctpClient.builder.types.where(`key="${type.key}"`)) - if (body.results.length === 0) - await ctpClient.create(ctpClient.builder.types, type) - } catch (e) { - console.error('Error when creating interface interaction custom type, skipping...', JSON.stringify(e)) + try { + logger.debug('Ensuring interfaceInteraction') + const { body } = await ctpClient.fetch(ctpClient.builder.types.where(`key="${interfaceInteractionType.key}"`)) + if (body.results.length === 0) { + logger.debug('Creating interface interaction') + await ctpClient.create(ctpClient.builder.types, interfaceInteractionType) + logger.info('Successfully created an interfaceInteraction type') } - }, { concurrency: 3 }) + } catch (e) { + logger.error(e, 'Error when creating interface interaction custom type, skipping...') + } } module.exports = { diff --git a/notification/src/handler/notification/notification.handler.js b/notification/src/handler/notification/notification.handler.js index 48b4fcf41..38cfc5d58 100644 --- a/notification/src/handler/notification/notification.handler.js +++ b/notification/src/handler/notification/notification.handler.js @@ -37,20 +37,22 @@ async function updatePaymentWithRepeater (payment, notification, ctpClient) { try { /* eslint-disable-next-line no-await-in-loop */ await ctpClient.update(ctpClient.builder.payments, currentPayment.id, currentVersion, updateActions) + logger.debug(`Payment with interfaceId ${currentPayment.interfaceId}` + + 'was successfully updated') break - } catch (err) { - if (err.body.statusCode !== 409) + } catch (e) { + if (e.body.statusCode !== 409) throw new Error(`Unexpected error during updating a payment with ID: ${currentPayment.id}. Exiting. ` - + `Error: ${JSON.stringify(serializeError(err))}`) + + `Error: ${JSON.stringify(serializeError(e))}`) retryCount += 1 if (retryCount > maxRetry) { retryMessage = 'Got a concurrent modification error' + ` when updating payment with id "${currentPayment.id}".` + ` Version tried "${currentVersion}",` - + ` currentVersion: "${err.body.errors[0].currentVersion}".` + + ` currentVersion: "${e.body.errors[0].currentVersion}".` throw new Error(`${retryMessage} Won't retry again` + ` because of a reached limit ${maxRetry}` - + ` max retries. Error: ${JSON.stringify(serializeError(err))}`) + + ` max retries. Error: ${JSON.stringify(serializeError(e))}`) } /* eslint-disable-next-line no-await-in-loop */ currentPayment = await ctpClient.fetchById(ctpClient.builder.payments, currentPayment.id) @@ -73,7 +75,10 @@ function calculateUpdateActionsForPayment (payment, notification) { if (isNotificationInInterfaceInteraction === false) updateActions.push(getAddInterfaceInteractionUpdateAction(notification)) - const { transactionType, transactionState } = getTransactionTypeAndStateOrNull(notificationEventCode, notificationSuccess) + const { + transactionType, + transactionState + } = getTransactionTypeAndStateOrNull(notificationEventCode, notificationSuccess) if (transactionType !== null) { // if there is already a transaction with type `transactionType` then update its `transactionState` if necessary, // otherwise create a transaction with type `transactionType` and state `transactionState` @@ -99,7 +104,7 @@ function getAddInterfaceInteractionUpdateAction (notification) { typeId: 'type' }, fields: { - timestamp: new Date(), + createdAt: new Date(), status: notification.NotificationRequestItem.eventCode, notification: JSON.stringify(notification) } @@ -115,8 +120,8 @@ function getChangeTransactionStateUpdateAction (transactionId, newTransactionSta } function getTransactionTypeAndStateOrNull (adyenEventCode, adyenEventSuccess) { - return _.find(adyenEvents, - adyenEvent => adyenEvent.eventCode === adyenEventCode && adyenEvent.success === adyenEventSuccess) + // eslint-disable-next-line max-len + return _.find(adyenEvents, adyenEvent => adyenEvent.eventCode === adyenEventCode && adyenEvent.success === adyenEventSuccess) || { eventCode: adyenEventCode, success: adyenEventSuccess, @@ -143,9 +148,9 @@ async function getPaymentByMerchantReference (merchantReference, ctpClient) { try { const result = await ctpClient.fetch(ctpClient.builder.payments.where(`interfaceId="${merchantReference}"`)) return _.get(result, 'body.results[0]', null) - } catch (err) { + } catch (e) { throw Error(`Failed to fetch a payment with merchantReference: ${merchantReference}. ` - + `Error: ${JSON.stringify(serializeError(err))}`) + + `Error: ${JSON.stringify(serializeError(e))}`) } } diff --git a/notification/src/init.js b/notification/src/init.js index 67e575ba5..ca225ae74 100644 --- a/notification/src/init.js +++ b/notification/src/init.js @@ -1,12 +1,14 @@ const server = require('./server.js').setupServer() const { ensureInterfaceInteractionCustomType } = require('./config/init/ensure-interface-interaction-custom-type') const ctp = require('./utils/ctp') +const logger = require('./utils/logger').getLogger() const config = require('./config/config')() + const ctpClient = ctp.get(config) const PORT = process.env.PORT || 443 server.listen(PORT, async () => { await ensureInterfaceInteractionCustomType(ctpClient) - console.log(`Server running at http://127.0.0.1:${PORT}/`) + logger.info(`Server started on ${PORT} port`) }) diff --git a/notification/src/server.js b/notification/src/server.js index f4247bae7..1c643438a 100644 --- a/notification/src/server.js +++ b/notification/src/server.js @@ -1,9 +1,9 @@ const http = require('http') const url = require('url') const utils = require('./utils/commons') -const logger = require('./utils/logger').getLogger() const { routes: defaultRoutes } = require('./routes') require('./config/config') +const logger = require('./utils/logger').getLogger() function setupServer (routes = defaultRoutes) { @@ -16,10 +16,10 @@ function setupServer (routes = defaultRoutes) { await route(request, response) } catch (e) { logger.error(e, `Unexpected error when processing URL ${JSON.stringify(parts)}`) - utils.sendResponse({ response, statusCode: 500 }) + utils.sendResponse(response, 500) } else - utils.sendResponse({ response, statusCode: 404 }) + utils.sendResponse(response, 404) }) } diff --git a/notification/src/utils/ctp.js b/notification/src/utils/ctp.js index 89bae69a1..2f14523be 100644 --- a/notification/src/utils/ctp.js +++ b/notification/src/utils/ctp.js @@ -37,7 +37,7 @@ function createCtpClient ({ authMiddleware, httpMiddleware, queueMiddleware - ], + ] }) } diff --git a/notification/src/utils/logger.js b/notification/src/utils/logger.js index 199836665..248178fcb 100644 --- a/notification/src/utils/logger.js +++ b/notification/src/utils/logger.js @@ -1,13 +1,15 @@ const bunyan = require('bunyan') +const { logLevel } = require('../config/config')() + let obj -function getLogger (logLevel) { - if( obj === undefined ) { +function getLogger () { + if (obj === undefined) { const NOTIFICATION_MODULE_NAME = 'ctp-adyen-integration-notifications' obj = bunyan.createLogger({ name: NOTIFICATION_MODULE_NAME, - stream: process.stderr, - level: logLevel || bunyan.ERROR + stream: process.stdout, + level: logLevel || bunyan.INFO }) } return obj diff --git a/notification/test/integration/init/init-resources.js b/notification/test/integration/init/init-resources.js index e77b2182c..abc32989d 100644 --- a/notification/test/integration/init/init-resources.js +++ b/notification/test/integration/init/init-resources.js @@ -1,4 +1,6 @@ -const { ensureInterfaceInteractionCustomType } = require('../../../src/config/init/ensure-interface-interaction-custom-type') +const { + ensureInterfaceInteractionCustomType +} = require('../../../src/config/init/ensure-interface-interaction-custom-type') const { ensurePayment } = require('./ensure-payment') async function ensureResources (ctpClient) { diff --git a/notification/test/integration/integration-test-set-up.js b/notification/test/integration/integration-test-set-up.js index 48bbb131c..192f12a5f 100644 --- a/notification/test/integration/integration-test-set-up.js +++ b/notification/test/integration/integration-test-set-up.js @@ -11,7 +11,6 @@ async function prepareProject (ctpClient) { async function startServer (testServerPort = 8000) { return new Promise(((resolve) => { server.listen(testServerPort, async () => { - console.log(`Server running at http://127.0.0.1:${testServerPort}/`) resolve() }) })) diff --git a/notification/test/integration/notification.handler.spec.js b/notification/test/integration/notification.handler.spec.js index b97180fc7..9f2bc1855 100644 --- a/notification/test/integration/notification.handler.spec.js +++ b/notification/test/integration/notification.handler.spec.js @@ -12,7 +12,7 @@ const notifications = require('../resources/notification') const localhostIp = address() describe('notification module', () => { - let ctpClient = ctpClientBuilder.get(config) + const ctpClient = ctpClientBuilder.get(config) before(async () => { await iTSetUp.startServer() @@ -31,7 +31,7 @@ describe('notification module', () => { }) it('should update the transaction state when receives a correct notification', async () => { - const { body: { results: [ paymentBefore ] }} = await ctpClient.fetch(ctpClient.builder.payments) + const { body: { results: [ paymentBefore ] } } = await ctpClient.fetch(ctpClient.builder.payments) expect(paymentBefore.transactions).to.have.lengthOf(1) expect(paymentBefore.transactions[0].type).to.equal('Authorization') expect(paymentBefore.transactions[0].state).to.equal('Initial') @@ -40,16 +40,16 @@ describe('notification module', () => { // Simulating a notification from Adyen const response = await fetch(`http://${localhostIp}:8000`, { method: 'post', - body: JSON.stringify(notifications), + body: JSON.stringify(notifications), headers: { 'Content-Type': 'application/json' }, }) - const status = response.status + const { status } = response const responseBody = await response.json() expect(responseBody).to.deep.equal({ notificationResponse: '[accepted]' }) expect(status).to.equal(200) - const { body: { results: [ paymentAfter ] }} = await ctpClient.fetch(ctpClient.builder.payments) + const { body: { results: [ paymentAfter ] } } = await ctpClient.fetch(ctpClient.builder.payments) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') expect(paymentAfter.transactions[0].state).to.equal('Success') @@ -58,9 +58,9 @@ describe('notification module', () => { expect(paymentAfter.interfaceInteractions[0].fields.notification).to.equal(JSON.stringify(notification)) }) - it('should not update transaction when the notification event ' + - 'is not mapped to any CTP payment state', async () => { - const { body: { results: [ paymentBefore ] }} = await ctpClient.fetch(ctpClient.builder.payments) + it('should not update transaction when the notification event ' + + 'is not mapped to any CTP payment state', async () => { + const { body: { results: [ paymentBefore ] } } = await ctpClient.fetch(ctpClient.builder.payments) expect(paymentBefore.transactions).to.have.lengthOf(1) expect(paymentBefore.transactions[0].type).to.equal('Authorization') expect(paymentBefore.transactions[0].state).to.equal('Initial') @@ -71,16 +71,16 @@ describe('notification module', () => { // Simulating a notification from Adyen const response = await fetch(`http://${localhostIp}:8000`, { method: 'post', - body: JSON.stringify(modifiedNotification), + body: JSON.stringify(modifiedNotification), headers: { 'Content-Type': 'application/json' }, }) - const status = response.status + const { status } = response const responseBody = await response.json() expect(responseBody).to.deep.equal({ notificationResponse: '[accepted]' }) expect(status).to.equal(200) - const { body: { results: [ paymentAfter ] }} = await ctpClient.fetch(ctpClient.builder.payments) + const { body: { results: [ paymentAfter ] } } = await ctpClient.fetch(ctpClient.builder.payments) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') expect(paymentAfter.transactions[0].state).to.equal('Initial') @@ -90,7 +90,7 @@ describe('notification module', () => { }) it('should response with success when payment does not exist on the platform', async () => { - const { body: { results: [ paymentBefore ] }} = await ctpClient.fetch(ctpClient.builder.payments) + const { body: { results: [ paymentBefore ] } } = await ctpClient.fetch(ctpClient.builder.payments) expect(paymentBefore.transactions).to.have.lengthOf(1) expect(paymentBefore.transactions[0].type).to.equal('Authorization') expect(paymentBefore.transactions[0].state).to.equal('Initial') @@ -101,16 +101,16 @@ describe('notification module', () => { // Simulating a notification from Adyen const response = await fetch(`http://${localhostIp}:8000`, { method: 'post', - body: JSON.stringify(modifiedNotification), + body: JSON.stringify(modifiedNotification), headers: { 'Content-Type': 'application/json' }, }) - const status = response.status + const { status } = response const responseBody = await response.json() expect(responseBody).to.deep.equal({ notificationResponse: '[accepted]' }) expect(status).to.equal(200) - const { body: { results: [ paymentAfter ] }} = await ctpClient.fetch(ctpClient.builder.payments) + const { body: { results: [ paymentAfter ] } } = await ctpClient.fetch(ctpClient.builder.payments) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') expect(paymentAfter.transactions[0].state).to.equal('Initial') diff --git a/notification/test/resources/payment-interface-interaction-types.json b/notification/test/resources/payment-interface-interaction-types.json deleted file mode 100644 index b24e86b6f..000000000 --- a/notification/test/resources/payment-interface-interaction-types.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "key": "ctp-adyen-integration-interaction-notification", - "name": { - "en": "ctp-adyen-integration-interaction-notification" - }, - "resourceTypeIds": [ - "payment-interface-interaction" - ], - "fieldDefinitions": [ - { - "name": "timestamp", - "label": { - "en": "timestamp" - }, - "required": true, - "type": { - "name": "String" - }, - "inputHint": "SingleLine" - }, - { - "name": "transactionId", - "label": { - "en": "transactionId" - }, - "required": false, - "type": { - "name": "String" - }, - "inputHint": "SingleLine" - }, - { - "name": "notification", - "label": { - "en": "notification" - }, - "required": true, - "type": { - "name": "String" - }, - "inputHint": "MultiLine" - }, - { - "name": "status", - "label": { - "en": "status" - }, - "required": false, - "type": { - "name": "String" - }, - "inputHint": "SingleLine" - } - ] - } -] diff --git a/notification/test/unit/ctp-client-mock.js b/notification/test/unit/ctp-client-mock.js index 72ec6f5b9..bb0a26b78 100644 --- a/notification/test/unit/ctp-client-mock.js +++ b/notification/test/unit/ctp-client-mock.js @@ -3,7 +3,6 @@ const { createClient } = require('@commercetools/sdk-client') const { createRequestBuilder } = require('@commercetools/api-request-builder') function createCtpClient () { - const httpMockSuccessMiddleware = next => (request, response) => { next(request, { ...response, body: { foo: 'bar' } }) } @@ -69,14 +68,14 @@ function getRequestBuilder (projectKey) { * */ function compareTransactionStates (currentState, newState) { const transactionStateFlow = { - "Initial": 0, - "Pending": 1, - "Success": 2, - "Failure": 2 + Initial: 0, + Pending: 1, + Success: 2, + Failure: 2 } - if(!transactionStateFlow.hasOwnProperty(currentState) || !transactionStateFlow.hasOwnProperty(newState)) - throw Error('Wrong transaction state passed. ' + - `currentState: ${currentState}, newState: ${newState}`) + if (!transactionStateFlow.hasOwnProperty(currentState) || !transactionStateFlow.hasOwnProperty(newState)) + throw Error('Wrong transaction state passed. ' + + `currentState: ${currentState}, newState: ${newState}`) if (transactionStateFlow[currentState] < transactionStateFlow[newState]) return 1 if (transactionStateFlow[currentState] > transactionStateFlow[newState]) @@ -86,6 +85,6 @@ function compareTransactionStates (currentState, newState) { module.exports = { - get: (config) => setUpClient(config), + get: config => setUpClient(config), compareTransactionStates } diff --git a/notification/test/unit/notification.handler.spec.js b/notification/test/unit/notification.handler.spec.js index d6494be56..2c25c0ca4 100644 --- a/notification/test/unit/notification.handler.spec.js +++ b/notification/test/unit/notification.handler.spec.js @@ -18,21 +18,17 @@ const config = { } describe('notification module', () => { - afterEach(() => sandbox.restore()) it('should update payment with a new InterfaceInteraction and payment status ' + 'when current payment does not have the interfaceInteraction and the transaction' + 'which are going to be set', async () => { - const ctpClient = ctpClientMock.get(config) - sandbox.stub(ctpClient, 'fetch').callsFake(() => { - return { - body: { - results: [paymentMock] - } + sandbox.stub(ctpClient, 'fetch').callsFake(() => ({ + body: { + results: [paymentMock] } - }) + })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') await notificationHandler.processNotifications(notificationsMock, ctpClient) const expectedUpdateActions = [ @@ -60,12 +56,12 @@ describe('notification module', () => { } ] - // Timestamp is set to the current date during the update action calculation + // createdAt is set to the current date during the update action calculation // We can't know what is set there - expect(ctpClientUpdateSpy.args[0][3][0].fields.timestamp).to.exist - const actualUpdateActionsWithoutTimestamp = ctpClientUpdateSpy.args[0][3] - delete actualUpdateActionsWithoutTimestamp[0].fields.timestamp - expect(actualUpdateActionsWithoutTimestamp).to.deep.equal(expectedUpdateActions) + expect(ctpClientUpdateSpy.args[0][3][0].fields.createdAt).to.exist + const actualUpdateActionsWithoutCreatedAt = ctpClientUpdateSpy.args[0][3] + delete actualUpdateActionsWithoutCreatedAt[0].fields.createdAt + expect(actualUpdateActionsWithoutCreatedAt).to.deep.equal(expectedUpdateActions) }) it('should update payment with a new InterfaceInteraction but not payment status ' @@ -73,23 +69,21 @@ describe('notification module', () => { + 'but has a transaction with the correct status', async () => { const modifiedPaymentMock = cloneDeep(paymentMock) modifiedPaymentMock.transactions.push({ - "type": "Authorization", - "amount": { - "type": "centPrecision", - "currencyCode": "EUR", - "centAmount": 495, - "fractionDigits": 2 + type: 'Authorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 495, + fractionDigits: 2 }, - "state": "Success" + state: 'Success' }) const ctpClient = ctpClientMock.get(config) - sandbox.stub(ctpClient, 'fetch').callsFake(() => { - return { - body: { - results: [modifiedPaymentMock] - } + sandbox.stub(ctpClient, 'fetch').callsFake(() => ({ + body: { + results: [modifiedPaymentMock] } - }) + })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') await notificationHandler.processNotifications(notificationsMock, ctpClient) const expectedUpdateActions = [ @@ -106,12 +100,12 @@ describe('notification module', () => { } ] - // Timestamp is set to the current date during the update action calculation + // createdAt is set to the current date during the update action calculation // We can't know what is set there - expect(ctpClientUpdateSpy.args[0][3][0].fields.timestamp).to.exist - const actualUpdateActionsWithoutTimestamp = ctpClientUpdateSpy.args[0][3] - delete actualUpdateActionsWithoutTimestamp[0].fields.timestamp - expect(actualUpdateActionsWithoutTimestamp).to.deep.equal(expectedUpdateActions) + expect(ctpClientUpdateSpy.args[0][3][0].fields.createdAt).to.exist + const actualUpdateActionsWithoutCreatedAt = ctpClientUpdateSpy.args[0][3] + delete actualUpdateActionsWithoutCreatedAt[0].fields.createdAt + expect(actualUpdateActionsWithoutCreatedAt).to.deep.equal(expectedUpdateActions) }) it('should update payment with a payment status but not new InterfaceInteraction ' @@ -126,17 +120,15 @@ describe('notification module', () => { fields: { notification: JSON.stringify(notificationsMock[0]), status: 'SUCCESS', - timestamp: '2019-02-05T12:29:36.028Z' + createdAt: '2019-02-05T12:29:36.028Z' } }) const ctpClient = ctpClientMock.get(config) - sandbox.stub(ctpClient, 'fetch').callsFake(() => { - return { - body: { - results: [modifiedPaymentMock] - } + sandbox.stub(ctpClient, 'fetch').callsFake(() => ({ + body: { + results: [modifiedPaymentMock] } - }) + })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') await notificationHandler.processNotifications(notificationsMock, ctpClient) const expectedUpdateActions = [ @@ -159,27 +151,25 @@ describe('notification module', () => { it('should update transaction with a new state', async () => { const modifiedPaymentMock = cloneDeep(paymentMock) const notificationsMockClone = cloneDeep(notificationsMock) - notificationsMockClone[0].NotificationRequestItem.eventCode = "CAPTURE" - notificationsMockClone[0].NotificationRequestItem.success = "false" + notificationsMockClone[0].NotificationRequestItem.eventCode = 'CAPTURE' + notificationsMockClone[0].NotificationRequestItem.success = 'false' modifiedPaymentMock.interfaceInteractions.push({ type: { typeId: 'type', id: '3fd15a04-b460-4a88-a911-0472c4c080b3' }, fields: { - timestamp: '2019-02-05T12:29:36.028Z', + createdAt: '2019-02-05T12:29:36.028Z', notification: JSON.stringify(notificationsMockClone[0]), status: 'SUCCESS' } }) const ctpClient = ctpClientMock.get(config) - sandbox.stub(ctpClient, 'fetch').callsFake(() => { - return { - body: { - results: [modifiedPaymentMock] - } + sandbox.stub(ctpClient, 'fetch').callsFake(() => ({ + body: { + results: [modifiedPaymentMock] } - }) + })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') @@ -203,33 +193,31 @@ describe('notification module', () => { id: '3fd15a04-b460-4a88-a911-0472c4c080b3' }, fields: { - timestamp: '2019-02-05T12:29:36.028Z', + createdAt: '2019-02-05T12:29:36.028Z', notification: JSON.stringify(notificationsMock[0]), status: 'SUCCESS' } }) const ctpClient = ctpClientMock.get(config) - sandbox.stub(ctpClient, 'fetch').callsFake(() => { - return { - body: { - results: [modifiedPaymentMock] - } + sandbox.stub(ctpClient, 'fetch').callsFake(() => ({ + body: { + results: [modifiedPaymentMock] } - }) - sandbox.stub(ctpClient, 'fetchById').callsFake(() => { - return { - body: { - results: [modifiedPaymentMock] - } + })) + sandbox.stub(ctpClient, 'fetchById').callsFake(() => ({ + body: { + results: [modifiedPaymentMock] } - }) + })) const ctpClientUpdateSpy = sandbox.stub(ctpClient, 'update').callsFake(() => { throw concurrentModificationError }) try { await notificationHandler.processNotifications(notificationsMock, ctpClient) - } catch { - + // eslint-disable-next-line no-empty + } catch (e) { + // we check retry logic here and it should throw after certain amount + // of retries. So the error is expected } expect(ctpClientUpdateSpy.callCount).to.equal(21) }) diff --git a/travis-build.sh b/travis-build.sh new file mode 100755 index 000000000..a4d095fc3 --- /dev/null +++ b/travis-build.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +set -e + +REPO=$1 +DOCKER_REPOSITORY="commercetools/commercetools-adyen-integration-${REPO}" + +cd ./$REPO/ +echo "Building Docker image using tag '${DOCKER_REPOSITORY}:${TRAVIS_TAG}'." +docker build -t "${DOCKER_REPOSITORY}:${TRAVIS_TAG}" . + +echo "Pushing Docker images to repository '${DOCKER_REPOSITORY}'." +docker push "${DOCKER_REPOSITORY}:${TRAVIS_TAG}"