Skip to content

Commit

Permalink
PP-4132 Added sub-segmenting to instrument the card_check request pat…
Browse files Browse the repository at this point in the history
…h, middleware and controller

- modified tests to add a @global stub for x-ray and continuation-local-storage that is not required in unit testing
  • Loading branch information
Mark Bridgett committed Aug 29, 2018
1 parent 1262d86 commit 05c81e0
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 96 deletions.
25 changes: 19 additions & 6 deletions app/controllers/charge_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
const logger = require('winston')
const _ = require('lodash')
const i18n = require('i18n')
const {getNamespace} = require('continuation-local-storage')
const AWSXRay = require('aws-xray-sdk')

// local dependencies
const logging = require('../utils/logging.js')
Expand Down Expand Up @@ -35,6 +37,7 @@ const AUTH_3DS_EPDQ_RESULTS = {
declined: 'DECLINED',
error: 'ERROR'
}
const clsXrayConfig = require('../../config/xray-cls')

function appendChargeForNewView (charge, req, chargeId) {
const cardModel = Card(charge.gatewayAccount.cardTypes, req.headers[CORRELATION_HEADER])
Expand Down Expand Up @@ -170,12 +173,22 @@ module.exports = {
})
},
checkCard: (req, res) => {
Card(req.chargeData.gateway_account.card_types, req.headers[CORRELATION_HEADER])
.checkCard(normalise.creditCard(req.body.cardNo))
.then(
() => res.json({'accepted': true}),
message => res.json({'accepted': false, message})
)
const namespace = getNamespace(clsXrayConfig.nameSpaceName)
const clsSegment = namespace.get(clsXrayConfig.segmentKeyName)
AWSXRay.captureAsyncFunc('Card_checkCard', function (subsegment) {
Card(req.chargeData.gateway_account.card_types, req.headers[CORRELATION_HEADER])
.checkCard(normalise.creditCard(req.body.cardNo), subsegment)
.then(
() => {
subsegment.close()
return res.json({'accepted': true})
},
message => {
subsegment.close(message)
return res.json({'accepted': false, message})
}
)
}, clsSegment)
},
authWaiting: (req, res) => {
const charge = normalise.charge(req.chargeData, req.chargeId)
Expand Down
30 changes: 21 additions & 9 deletions app/middleware/retrieve_charge.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
'use strict'

// NPM dependencies
const AWSXRay = require('aws-xray-sdk')
const {getNamespace} = require('continuation-local-storage')

// local dependencies
const views = require('../utils/views.js')
const Charge = require('../models/charge.js')
const chargeParam = require('../services/charge_param_retriever.js')
const CORRELATION_HEADER = require('../utils/correlation_header.js').CORRELATION_HEADER
const withAnalyticsError = require('../utils/analytics.js').withAnalyticsError

// constants
const clsXrayConfig = require('../../config/xray-cls')

module.exports = (req, res, next) => {
const chargeId = chargeParam.retrieve(req)

const namespace = getNamespace(clsXrayConfig.nameSpaceName)
const clsSegment = namespace.get(clsXrayConfig.segmentKeyName)
if (!chargeId) {
views.display(res, 'UNAUTHORISED', withAnalyticsError())
} else {
req.chargeId = chargeId
Charge(req.headers[CORRELATION_HEADER]).find(chargeId)
.then(data => {
req.chargeData = data
next()
})
.catch(() => {
views.display(res, 'SYSTEM_ERROR', withAnalyticsError())
})
AWSXRay.captureAsyncFunc('Charge_find', function (subsegment) {
Charge(req.headers[CORRELATION_HEADER]).find(chargeId)
.then(data => {
subsegment.close()
req.chargeData = data
next()
})
.catch(() => {
subsegment.close('error')
views.display(res, 'SYSTEM_ERROR', withAnalyticsError())
})
}, clsSegment)
}
}
2 changes: 1 addition & 1 deletion app/middleware/x_ray.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

// NPM dependencies
const getNamespace = require('continuation-local-storage').getNamespace
const {getNamespace} = require('continuation-local-storage')

// Local dependencies
const clsXrayConfig = require('../../config/xray-cls')
Expand Down
78 changes: 47 additions & 31 deletions app/models/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,65 @@ const _ = require('lodash')
const q = require('q')
const changeCase = require('change-case')
const logger = require('winston')
const AWSXRay = require('aws-xray-sdk')
const {getNamespace} = require('continuation-local-storage')

// local dependencies
const cardIdClient = require('../utils/cardid_client')

const checkCard = function (cardNo, allowed, correlationId) {
const defer = q.defer()
// Constants
const clsXrayConfig = require('../../config/xray-cls')

const checkCard = function (cardNo, allowed, correlationId, subSegment) {
const defer = q.defer()
const startTime = new Date()
const data = {'cardNumber': parseInt(cardNo)}

cardIdClient.post({data: data, correlationId: correlationId}, function (data, response) {
logger.info(`[${correlationId}] - %s to %s ended - total time %dms`, 'POST', cardIdClient.CARD_URL, new Date() - startTime)
// Use a subSegment if passed, otherwise get our main segment
if (!subSegment) {
const namespace = getNamespace(clsXrayConfig.nameSpaceName)
subSegment = namespace.get(clsXrayConfig.segmentKeyName)
}

if (response.statusCode === 404) {
return defer.reject('Your card is not supported')
}
// if the server is down, or returns non 500, just continue
if (response.statusCode !== 200) {
return defer.resolve()
}
AWSXRay.captureAsyncFunc('cardIdClient_post', function (postSubsegment) {
cardIdClient.post({data: data, correlationId: correlationId}, function (data, response) {
postSubsegment.close()
logger.info(`[${correlationId}] - %s to %s ended - total time %dms`, 'POST', cardIdClient.CARD_URL, new Date() - startTime)

const cardBrand = changeCase.paramCase(data.brand)
const cardType = normaliseCardType(data.type)
if (response.statusCode === 404) {
return defer.reject('Your card is not supported')
}
// if the server is down, or returns non 500, just continue
if (response.statusCode !== 200) {
return defer.resolve()
}

const cardBrand = changeCase.paramCase(data.brand)
const cardType = normaliseCardType(data.type)

logger.debug(`[${correlationId}] Checking card brand - `, {'cardBrand': cardBrand, 'cardType': cardType})
logger.debug(`[${correlationId}] Checking card brand - `, {'cardBrand': cardBrand, 'cardType': cardType})

const brandExists = _.filter(allowed, {brand: cardBrand}).length > 0
if (!brandExists) defer.reject(changeCase.titleCase(cardBrand) + ' is not supported')
const brandExists = _.filter(allowed, {brand: cardBrand}).length > 0
if (!brandExists) defer.reject(changeCase.titleCase(cardBrand) + ' is not supported')

const cardObject = _.find(allowed, {brand: cardBrand, type: cardType})
const cardObject = _.find(allowed, {brand: cardBrand, type: cardType})

if (!cardObject) {
switch (cardType) {
case 'DEBIT':
return defer.reject(changeCase.titleCase(cardBrand) + ' debit cards are not supported')
case 'CREDIT':
return defer.reject(changeCase.titleCase(cardBrand) + ' credit cards are not supported')
if (!cardObject) {
switch (cardType) {
case 'DEBIT':
return defer.reject(changeCase.titleCase(cardBrand) + ' debit cards are not supported')
case 'CREDIT':
return defer.reject(changeCase.titleCase(cardBrand) + ' credit cards are not supported')
}
}
}
return defer.resolve(cardBrand)
}).on('error', function (error) {
logger.error(`[${correlationId}] ERROR CALLING CARDID AT ${cardIdClient.CARD_URL}`, error)
logger.info(`[${correlationId}] - %s to %s ended - total time %dms`, 'POST', cardIdClient.cardUrl, new Date() - startTime)
defer.resolve()
})
return defer.resolve(cardBrand)
}, postSubsegment).on('error', function (error) {
postSubsegment.close(error)
logger.error(`[${correlationId}] ERROR CALLING CARDID AT ${cardIdClient.CARD_URL}`, error)
logger.info(`[${correlationId}] - %s to %s ended - total time %dms`, 'POST', cardIdClient.cardUrl, new Date() - startTime)
defer.resolve()
})
}, subSegment)
return defer.promise
}

Expand All @@ -74,6 +88,8 @@ module.exports = function (allowedCards, correlationId) {
return {
withdrawalTypes: withdrawalTypes,
allowed: _.clone(allowed),
checkCard: (cardNo) => { return checkCard(cardNo, allowed, correlationId) }
checkCard: (cardNo, subSegment) => {
return checkCard(cardNo, allowed, correlationId, subSegment)
}
}
}
4 changes: 2 additions & 2 deletions app/models/charge.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ module.exports = function (correlationId) {
return defer.promise
}

const find = function (chargeId) {
const find = function (chargeId, subSegment) {
const defer = q.defer()
const url = connectorurl('show', {chargeId: chargeId})

Expand All @@ -78,7 +78,7 @@ module.exports = function (correlationId) {
return defer.reject(new Error('GET_FAILED'))
}
defer.resolve(data)
}).on('error', function (err) {
}, subSegment).on('error', function (err) {
logger.info('[%s] - %s to %s ended - total time %dms', correlationId, 'GET', url, new Date() - startTime)
logger.error('[%s] Calling connector to get charge threw exception -', correlationId, {
service: 'connector',
Expand Down
23 changes: 12 additions & 11 deletions app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ exports.bind = function (app) {

const middlewareStack = [
xraySegmentCls,
resolveLanguage,
csrfCheck,
csrfTokenGeneration,
actionName,
Expand All @@ -73,28 +72,30 @@ exports.bind = function (app) {
app.get(card.authWaiting.path, middlewareStack, charge.authWaiting)
app.get(card.auth3dsRequired.path, middlewareStack, charge.auth3dsRequired)
app.get(card.auth3dsRequiredOut.path, middlewareStack, charge.auth3dsRequiredOut)
app.post(card.auth3dsRequiredInEpdq.path, [csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredInEpdq)
app.get(card.auth3dsRequiredInEpdq.path, [csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredInEpdq)
app.post(card.auth3dsRequiredIn.path, [csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredIn)
app.get(card.auth3dsRequiredIn.path, [csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredIn)
app.post(card.auth3dsRequiredInEpdq.path, [xraySegmentCls, csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredInEpdq)
app.get(card.auth3dsRequiredInEpdq.path, [xraySegmentCls, csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredInEpdq)
app.post(card.auth3dsRequiredIn.path, [xraySegmentCls, csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredIn)
app.get(card.auth3dsRequiredIn.path, [xraySegmentCls, csrfTokenGeneration, retrieveCharge], charge.auth3dsRequiredIn)
app.post(card.auth3dsHandler.path, middlewareStack, charge.auth3dsHandler)
app.get(card.captureWaiting.path, middlewareStack, charge.captureWaiting)
app.post(card.create.path, middlewareStack, charge.create)
app.get(card.confirm.path, middlewareStack, charge.confirm)
app.post(card.capture.path, middlewareStack, charge.capture)
app.post(card.cancel.path, middlewareStack, charge.cancel)
app.post(card.checkCard.path, retrieveCharge, charge.checkCard)
app.get(card.return.path, retrieveCharge, returnCont.return)
app.post(card.checkCard.path, xraySegmentCls, retrieveCharge, charge.checkCard)
app.get(card.return.path, xraySegmentCls, retrieveCharge, returnCont.return)

// secure controller
app.get(paths.secure.get.path, secure.new)
app.post(paths.secure.post.path, secure.new)
app.get(paths.secure.get.path, xraySegmentCls, secure.new)
app.post(paths.secure.post.path, xraySegmentCls, secure.new)

// static controller
app.get(paths.static.humans.path, statik.humans)
app.all(paths.static.naxsi_error.path, statik.naxsi_error)
app.get(paths.static.humans.path, xraySegmentCls, statik.humans)
app.all(paths.static.naxsi_error.path, xraySegmentCls, statik.naxsi_error)

// route to gov.uk 404 page
// this has to be the last route registered otherwise it will redirect other routes
app.all('*', (req, res) => res.redirect('https://www.gov.uk/404'))

app.use(AWSXRay.express.closeSegment())
}
10 changes: 5 additions & 5 deletions app/utils/base_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const urlParse = require('url')
const logger = require('winston')
const https = setHttpClient()
const _ = require('lodash')
const getNamespace = require('continuation-local-storage').getNamespace
const {getNamespace} = require('continuation-local-storage')
const AWSXRay = require('aws-xray-sdk')

// Local dependencies
Expand Down Expand Up @@ -131,8 +131,8 @@ module.exports = {
*
* @returns {OutgoingMessage}
*/
get: function (url, args, callback, subsegment) {
return _request('GET', url, args, callback, subsegment)
get: function (url, args, callback, subSegment) {
return _request('GET', url, args, callback, subSegment)
},

/**
Expand All @@ -143,8 +143,8 @@ module.exports = {
*
* @returns {OutgoingMessage}
*/
post: function (url, args, callback) {
return _request('POST', url, args, callback)
post: function (url, args, callback, subSegment) {
return _request('POST', url, args, callback, subSegment)
},

/**
Expand Down
2 changes: 1 addition & 1 deletion app/utils/base_client2.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const urlParse = require('url').parse
const _ = require('lodash')
const logger = require('winston')
const request = require('requestretry')
const getNamespace = require('continuation-local-storage').getNamespace
const {getNamespace} = require('continuation-local-storage')
const AWSXRay = require('aws-xray-sdk')

// Local dependencies
Expand Down
2 changes: 1 addition & 1 deletion app/utils/cardid_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ const CARD_URL = process.env.CARDID_HOST + '/v1/api/card'
*
* @returns {Request}
*/
exports.post = (args, callBack) => baseClient.post(CARD_URL, args, callBack)
exports.post = (args, callBack, subSegment) => baseClient.post(CARD_URL, args, callBack, subSegment)
exports.CARD_URL = CARD_URL
2 changes: 1 addition & 1 deletion aws-xray.rules
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
],
"default": {
"fixed_target": 1,
"rate": 0.1
"rate": 0.01
},
"version": 1
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 05c81e0

Please sign in to comment.