Skip to content

Commit

Permalink
Merge pull request #900 from recurly/braintree-proactive-3-d-secure-a…
Browse files Browse the repository at this point in the history
…djust

Simplifies proactive 3-D Secure APIs
  • Loading branch information
gilv93 authored Sep 27, 2024
2 parents a6069aa + 9a728f3 commit c453612
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 81 deletions.
1 change: 1 addition & 0 deletions lib/recurly.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const DEFAULTS = {
preflightDeviceDataCollector: true,
proactive: {
enabled: false,
gatewayCode: ''
}
}
},
Expand Down
28 changes: 15 additions & 13 deletions lib/recurly/risk/risk.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,27 @@ export class Risk {
* @return {Promise}
*/
static preflight ({ recurly, number, month, year, cvv }) {
function resolveRoute (recurly) {
let route = '/risk/preflights';
if (recurly.config.risk.threeDSecure.proactive.enabled) {
route += `?proactive=true&gatewayCode=${recurly.config.risk.threeDSecure.proactive.gateway_code}`;
}
return route;
const data = {};

if (recurly.config.risk.threeDSecure.proactive.enabled) {
data.proactive = true;
data.gateway_code = recurly.config.risk.threeDSecure.proactive.gatewayCode;
}

return recurly.request.get({ route: resolveRoute(recurly) })
return recurly.request.get({ route: '/risk/preflights', data })
.then(({ preflights }) => {
debug('received preflight instructions', preflights);
return ThreeDSecure.preflight({ recurly, number, month, year, cvv, preflights });
})
.then(results => results.filter(maybeErr => {
if (maybeErr.code === 'risk-preflight-timeout') {
debug('timeout encountered', maybeErr);
return false;
}
return true;
.then(({ tokenType, risk }) => ({
risk: risk.filter(maybeErr => {
if (maybeErr.code === 'risk-preflight-timeout') {
debug('timeout encountered', maybeErr);
return false;
}
return true;
}),
tokenType
}));
}

Expand Down
26 changes: 15 additions & 11 deletions lib/recurly/risk/three-d-secure/strategy/braintree.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,35 @@ export default class BraintreeStrategy extends ThreeDSecureStrategy {
}

static preflight ({ recurly, number, month, year, cvv }) {
const { proactive } = recurly.config.risk.threeDSecure;
const { enabled, gatewayCode, amount } = recurly.config.risk.threeDSecure.proactive;

if(!proactive.enabled) {
debug('performing preflight for', { gatewayCode });

if (!enabled) {
return Promise.resolve();
}

const data = {
gatewayType: BraintreeStrategy.strategyName,
gatewayCode: proactive.gateway_code,
gateway_type: BraintreeStrategy.strategyName,
gateway_code: gatewayCode,
number,
month,
year,
cvv,
cvv
};

// we don't really need to do anything once we get a response except resolve with relevant data instead of session_id
// we don't really need to do anything once we get a response except
// resolve with relevant data instead of session_id
return recurly.request.post({ route: '/risk/authentications', data })
.then(({ paymentMethodNonce, clientToken, bin }) => (
{
.then(({ paymentMethodNonce, clientToken, bin }) => ({
results: {
payment_method_nonce: paymentMethodNonce,
client_token: clientToken,
bin,
proactive: true
}
));
amount: amount
},
tokenType: 'three_d_secure_proactive_action'
}));
}


Expand Down
2 changes: 1 addition & 1 deletion lib/recurly/risk/three-d-secure/strategy/cybersource.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class CybersourceStrategy extends ThreeDSecureStrategy {
const body = JSON.parse(data);
if (body.MessageType === 'profile.completed') {
debug('received device data session id', body);
resolve({ session_id: body.SessionId });
resolve({ results: { session_id: body.SessionId }});
frame.destroy();
recurly.bus.off('raw-message', listener);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/recurly/risk/three-d-secure/strategy/worldpay.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class WorldpayStrategy extends ThreeDSecureStrategy {
const body = JSON.parse(data);
if (body.MessageType === 'profile.completed') {
debug('received device data session id', body);
resolve({ session_id: body.SessionId });
resolve({ results: { session_id: body.SessionId }});
recurly.bus.off('raw-message', listener);
frame.destroy();
}
Expand Down
76 changes: 38 additions & 38 deletions lib/recurly/risk/three-d-secure/three-d-secure.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export class ThreeDSecure extends RiskConcern {
'05': { height: '100%', width: '100%' }
}

static VALID_ACTION_TOKEN_TYPES = [
'three_d_secure_action',
'three_d_secure_proactive_action'
];

/**
* Returns a strateggy for a given gateway type
*
Expand All @@ -97,18 +102,32 @@ export class ThreeDSecure extends RiskConcern {
static preflight ({ recurly, number, month, year, cvv, preflights }) {
return preflights.reduce((preflight, result) => {
return preflight.then((finishedPreflights) => {
const { type } = result.gateway;
const { type: gatewayType } = result.gateway;
const { gateway_code } = result.params;
const strategy = ThreeDSecure.getStrategyForGatewayType(type);
const strategy = ThreeDSecure.getStrategyForGatewayType(gatewayType);
return strategy.preflight({ recurly, number, month, year, cvv, ...result.params })
.then(results => {
return finishedPreflights.concat([{ processor: type, gateway_code, results }]);
.then(({ results, tokenType }) => {
// return finishedPreflights.concat([{ processor: type, gateway_code, results}]);
return {
tokenType: finishedPreflights.tokenType || tokenType,
// risk: {
// processor: gatewayType,
// gateway_code,
// risk
// // finishedPreflights.risk.concat(risk)
// }
risk: finishedPreflights.risk.concat({
processor: gatewayType,
gateway_code,
results
})
}
});
});
}, Promise.resolve([]));
}, Promise.resolve({risk: []}));
}

constructor ({ risk, actionTokenId, challengeWindowSize, proactiveTokenId }) {
constructor ({ risk, actionTokenId, challengeWindowSize }) {
const existingConcern = risk.concerns.find((concern) => concern instanceof ThreeDSecure);
if (existingConcern) {
throw errors('3ds-multiple-instances', { name: 'ThreeDSecure', expect: 'to be the only concern' });
Expand All @@ -117,28 +136,25 @@ export class ThreeDSecure extends RiskConcern {
super({ risk });

this.actionTokenId = actionTokenId;
this.proactiveTokenId = proactiveTokenId;

this.validateChallengeWindowSize(challengeWindowSize);
this.challengeWindowSize = challengeWindowSize || this.constructor.CHALLENGE_WINDOW_SIZE_DEFAULT;

if (!actionTokenId && !proactiveTokenId) {
if (!actionTokenId) {
throw errors('invalid-option', { name: 'actionTokenId', expect: 'a three_d_secure_action_token_id' });
}

this.recurly.request.get({ route: `/tokens/${this.resolveToken()}` })
this.recurly.request.get({ route: `/tokens/${actionTokenId}` })
.catch(err => this.error(err))
.then(token => {
if (this.resolveToken() == this.actionTokenId) {
this.resolveActionToken(token);
} else {
this.resolveProactiveToken(token);
}
assertIsActionToken(token);
this.strategy = this.getStrategyForActionToken(token);
this.strategy.on('done', (...args) => this.onStrategyDone(...args));
this.markReady();
})
.catch(err => this.error(err));

this.report('create', { actionTokenId, proactiveTokenId });
this.report('create', { actionTokenId });
this.whenReady(() => this.report('ready', { strategy: this.strategy.strategyName }));
}

Expand Down Expand Up @@ -172,11 +188,6 @@ export class ThreeDSecure extends RiskConcern {
return new strategy({ threeDSecure: this, actionToken });
}

getStrategyForProactiveToken (token) {
const strategy = ThreeDSecure.getStrategyForGatewayType(token.three_d_secure.gateway.type);
return new strategy({ threeDSecure: this, proactiveToken: token });
}

/**
* Creates a ThreeDSecureActionResultToken from action results
*
Expand All @@ -189,9 +200,9 @@ export class ThreeDSecure extends RiskConcern {
const data = {
type: 'three_d_secure_action_result',
three_d_secure_action_token_id: this.actionTokenId,
proactive_three_d_secure_token_id: this.proactiveTokenId,
results
};

debug('submitting results for tokenization', data);
return this.recurly.request.post({ route: '/tokens', data });
}
Expand Down Expand Up @@ -225,24 +236,13 @@ export class ThreeDSecure extends RiskConcern {
throw new Error(`Invalid challengeWindowSize. Expected any of ${validWindowSizes}, got ${challengeWindowSize}`);
}
}

resolveToken () {
return this.actionTokenId || this.proactiveTokenId;
}

resolveActionToken (token) {
assertIsActionToken(token);
this.strategy = this.getStrategyForActionToken(token);
this.strategy.on('done', (...args) => this.onStrategyDone(...args));
}

resolveProactiveToken (token) {
this.strategy = this.getStrategyForProactiveToken(token);
this.strategy.on('done', (...args) => this.onStrategyDone(...args));
}
}

function assertIsActionToken (token) {
if (token && token.type === 'three_d_secure_action') return;
throw errors('invalid-option', { name: 'actionTokenId', expect: 'a three_d_secure_action_token_id' });
if (ThreeDSecure.VALID_ACTION_TOKEN_TYPES.includes(token?.type)) return;

throw errors('invalid-option', {
name: 'actionTokenId',
expect: `a token of type: ${ThreeDSecure.VALID_ACTION_TOKEN_TYPES.join(',')}`
});
}
20 changes: 3 additions & 17 deletions lib/recurly/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ function token (customerData, bus, done) {

const { number, month, year, cvv } = inputs;
Risk.preflight({ recurly: this, number, month, year, cvv })
.then(results => {
enrichInputs(this, inputs, results);
.then(({ risk, tokenType }) => {
inputs.risk = risk
if (tokenType) inputs.type = tokenType
})
.then(() => this.request.post({ route: '/token', data: inputs, done: complete }))
.done();
Expand All @@ -188,19 +189,4 @@ function token (customerData, bus, done) {
}
done(null, res);
}

function enrichInputs (recurly, inputs, results) {
if (results.length === 0) return;

inputs.risk = [];

results.forEach(result => {
if (result.processor === 'braintree_blue') {
inputs.proactive = recurly.config.risk.threeDSecure.proactive;
inputs.proactive.params = result;
} else {
inputs.risk.push(result);
}
});
}
}
1 change: 1 addition & 0 deletions types/lib/configure.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type RecurlyOptions = {
preflightDeviceDataCollector?: boolean;
proactive?: {
enabled: true;
gatewayCode: string;
}
}
};
Expand Down

0 comments on commit c453612

Please sign in to comment.