Skip to content

Commit

Permalink
Merge pull request #535 from recurly/3d-secure-wirecard
Browse files Browse the repository at this point in the history
Adds Wirecard 3D Secure support
  • Loading branch information
snodgrass23 authored Aug 4, 2019
2 parents d1c5d71 + 1325d4d commit ee3f1ec
Show file tree
Hide file tree
Showing 22 changed files with 389 additions and 84 deletions.
33 changes: 17 additions & 16 deletions lib/recurly/risk/risk.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,35 @@ export class Risk {
ThreeDSecure = ThreeDSecure;

/**
* Returns a set of values helpful for building a risk profile
* Returns a set of values helpful for building a risk profile,
* formatted for the API
*
* @typedef {Object} BrowserInfo
* @property {Number} colorDepth
* @property {Boolean} javaEnabled
* @property {Number} color_depth
* @property {Boolean} java_enabled
* @property {String} language
* @property {Number} screenHeight
* @property {Number} screenWidth
* @property {Number} timeZoneOffset
* @property {String} userAgent
* @property {Number} screen_height
* @property {Number} screen_width
* @property {Number} time_zone_offset
* @property {String} user_agent
*
* @private
* @return {BrowserInfo}
*/
static get browserInfo () {
const { navigator, screen } = window;
const { language, userAgent } = navigator;
const { colorDepth } = screen;
const { language, userAgent: user_agent } = navigator;
const { colorDepth: color_depth } = screen;

return {
colorDepth,
javaEnabled: navigator.javaEnabled(),
color_depth,
java_enabled: navigator.javaEnabled(),
language,
referrerUrl: window.location.href,
screenHeight: screen.height,
screenWidth: screen.width,
timeZoneOffset: (new Date()).getTimezoneOffset(),
userAgent
referrer_url: window.location.href,
screen_height: screen.height,
screen_width: screen.width,
time_zone_offset: (new Date()).getTimezoneOffset(),
user_agent
};
}

Expand Down
13 changes: 6 additions & 7 deletions lib/recurly/risk/three-d-secure/strategy/adyen.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import Promise from 'promise';
import ThreeDSecureStrategy from './strategy';

const debug = require('debug')('recurly:risk:three-d-secure:adyen');
const LIB_URL = 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/2.2.0/adyen.js';

export default class AdyenStrategy extends ThreeDSecureStrategy {
strategyName = 'adyen';
static libUrl = 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/2.2.0/adyen.js';
static strategyName = 'adyen';

constructor (...args) {
super(...args);
Expand All @@ -19,8 +19,7 @@ export default class AdyenStrategy extends ThreeDSecureStrategy {
this.adyenCheckout = new window.AdyenCheckout();
debug('Adyen checkout instance created', this.adyenCheckout);
this.markReady();
})
.done();
});
}

get shouldLoadAdyenLibrary () {
Expand Down Expand Up @@ -135,7 +134,7 @@ export default class AdyenStrategy extends ThreeDSecureStrategy {
...adyenRedirectParams.data
};
this.frame = recurly.Frame({ type: 'iframe', path: '/three_d_secure/start', payload, container })
.on('error', cause => this.threeDSecure.error('3ds-auth-error', { cause }))
.on('error', cause => threeDSecure.error('3ds-auth-error', { cause }))
.on('done', results => this.emit('done', results));
}

Expand All @@ -145,8 +144,8 @@ export default class AdyenStrategy extends ThreeDSecureStrategy {
loadAdyenLibrary () {
return new Promise((resolve, reject) => {
if (window.AdyenCheckout) return resolve();
loadScript(LIB_URL, error => {
if (error) reject(err);
loadScript(AdyenStrategy.libUrl, error => {
if (error) reject(error);
else resolve();
})
});
Expand Down
2 changes: 1 addition & 1 deletion lib/recurly/risk/three-d-secure/strategy/strategy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ReadinessEmitter from '../../../../util/readiness-emitter';

export default class ThreeDSecureStrategy extends ReadinessEmitter {
strategyName = 'base';
static strategyName = 'base';

constructor ({ threeDSecure, actionToken }) {
super();
Expand Down
7 changes: 3 additions & 4 deletions lib/recurly/risk/three-d-secure/strategy/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import ThreeDSecureStrategy from './strategy';
const debug = require('debug')('recurly:risk:three-d-secure:stripe');

export default class StripeStrategy extends ThreeDSecureStrategy {
static LIB_URL = 'https://js.stripe.com/v3/';

strategyName = 'stripe';
static libUrl = 'https://js.stripe.com/v3/';
static strategyName = 'stripe';

constructor (...args) {
super(...args);
Expand Down Expand Up @@ -56,7 +55,7 @@ export default class StripeStrategy extends ThreeDSecureStrategy {
loadStripeLibrary () {
return new Promise((resolve, reject) => {
if (window.Stripe) return resolve();
loadScript(StripeStrategy.LIB_URL, error => {
loadScript(StripeStrategy.libUrl, error => {
if (error) reject(error);
else resolve();
})
Expand Down
2 changes: 1 addition & 1 deletion lib/recurly/risk/three-d-secure/strategy/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ThreeDSecureStrategy from './strategy';
const debug = require('debug')('recurly:risk:three-d-secure:test');

export default class TestStrategy extends ThreeDSecureStrategy {
strategyName = 'test';
static strategyName = 'test';

constructor (...args) {
super(...args);
Expand Down
48 changes: 48 additions & 0 deletions lib/recurly/risk/three-d-secure/strategy/wirecard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Promise from 'promise';
import ThreeDSecureStrategy from './strategy';

const debug = require('debug')('recurly:risk:three-d-secure:wirecard');

export default class WirecardStrategy extends ThreeDSecureStrategy {
static strategyName = 'wirecard';

constructor (...args) {
super(...args);
this.markReady();
}

get redirectParams () {
return this.actionToken.three_d_secure.params;
}

/**
* Provides the target DOM element for which we will apply
* fingerprint detection, challenge flows, and results
*
* @param {HTMLElement} element
*/
attach (element) {
super.attach(element);
debug('Initiating 3D Secure frame');

const { redirectParams, container, threeDSecure } = this;
const { recurly } = threeDSecure.risk;
const payload = {
redirect_url: redirectParams.acsUrl,
pa_req: redirectParams.pareq
};

this.frame = recurly.Frame({ path: '/three_d_secure/start', payload, container })
.on('error', cause => threeDSecure.error('3ds-auth-error', { cause }))
.on('done', results => this.emit('done', results));
}

/**
* Removes DOM elements
*/
remove () {
const { frame } = this;
if (frame) frame.destroy();
super.remove();
}
}
20 changes: 12 additions & 8 deletions lib/recurly/risk/three-d-secure/three-d-secure.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import AdyenStrategy from './strategy/adyen';
import errors from '../../errors';
import find from 'component-find';
import Promise from 'promise';
import RiskConcern from '../risk-concern';
import StripeStrategy from './strategy/stripe';
import TestStrategy from './strategy/test';
import ThreeDSecureStrategy from './strategy';
import WirecardStrategy from './strategy/wirecard';

const debug = require('debug')('recurly:risk:three-d-secure');

Expand Down Expand Up @@ -34,11 +36,12 @@ export function factory (options) {
export class ThreeDSecure extends RiskConcern {
concernName = 'three-d-secure';

static STRATEGY_MAP = {
adyen: AdyenStrategy,
stripe: StripeStrategy,
test: TestStrategy
};
static STRATEGIES = [
AdyenStrategy,
StripeStrategy,
TestStrategy,
WirecardStrategy
];

constructor ({ risk, actionTokenId }) {
super({ risk });
Expand Down Expand Up @@ -91,9 +94,10 @@ export class ThreeDSecure extends RiskConcern {
* @param {Object} actionToken
*/
getStrategyForActionToken (actionToken) {
const strategy = this.constructor.STRATEGY_MAP[actionToken.gateway.type];
const threeDSecure = this;
return new strategy({ threeDSecure, actionToken });
const { STRATEGIES } = this.constructor;
const { type } = actionToken.gateway;
const strategy = find(STRATEGIES, s => s.strategyName === type);
return new strategy({ threeDSecure: this, actionToken });
}

/**
Expand Down
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.

28 changes: 14 additions & 14 deletions test/risk.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,24 @@ describe('Risk', function () {
it('returns an object containing risk-related browser info', function () {
const browserInfo = Risk.browserInfo;
assert.deepStrictEqual(Object.keys(browserInfo), [
'colorDepth',
'javaEnabled',
'color_depth',
'java_enabled',
'language',
'referrerUrl',
'screenHeight',
'screenWidth',
'timeZoneOffset',
'userAgent'
'referrer_url',
'screen_height',
'screen_width',
'time_zone_offset',
'user_agent'
]);

assert.strictEqual(browserInfo.colorDepth, 'fake-color-depth');
assert.strictEqual(browserInfo.javaEnabled, 'fake-java-enabled');
assert.strictEqual(browserInfo.color_depth, 'fake-color-depth');
assert.strictEqual(browserInfo.java_enabled, 'fake-java-enabled');
assert.strictEqual(browserInfo.language, 'fake-language');
assert.strictEqual(browserInfo.referrerUrl, location.href);
assert.strictEqual(browserInfo.screenHeight, 'fake-height');
assert.strictEqual(browserInfo.screenWidth, 'fake-width');
assert.strictEqual(browserInfo.timeZoneOffset, 'fake-timezone-offset');
assert.strictEqual(browserInfo.userAgent, navigator.userAgent);
assert.strictEqual(browserInfo.referrer_url, location.href);
assert.strictEqual(browserInfo.screen_height, 'fake-height');
assert.strictEqual(browserInfo.screen_width, 'fake-width');
assert.strictEqual(browserInfo.time_zone_offset, 'fake-timezone-offset');
assert.strictEqual(browserInfo.user_agent, navigator.userAgent);
});
});

Expand Down
30 changes: 10 additions & 20 deletions test/risk/three-d-secure.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import AdyenStrategy from '../../lib/recurly/risk/three-d-secure/strategy/adyen'
import StripeStrategy from '../../lib/recurly/risk/three-d-secure/strategy/stripe';
import TestStrategy from '../../lib/recurly/risk/three-d-secure/strategy/test';
import ThreeDSecureStrategy from '../../lib/recurly/risk/three-d-secure/strategy';
import WirecardStrategy from '../../lib/recurly/risk/three-d-secure/strategy/wirecard';

describe('ThreeDSecure', function () {
this.ctx.fixture = 'threeDSecure';

applyFixtures();

beforeEach(function (done) {
const actionTokenId = this.actionTokenId = 'test-action-token-id';
const actionTokenId = this.actionTokenId = 'action-token-test';
const recurly = this.recurly = initRecurly();
const risk = this.risk = { add: sinon.stub(), remove: sinon.stub(), recurly };
const sandbox = this.sandbox = sinon.createSandbox();
Expand Down Expand Up @@ -73,12 +74,12 @@ describe('ThreeDSecure', function () {
it('sets the strategy according to the gateway type of the action token', function (done) {
const { risk } = this;
[
{ id: 'adyen-action-token-id', strategy: AdyenStrategy },
{ id: 'stripe-action-token-id', strategy: StripeStrategy },
{ id: 'test-action-token-id', strategy: TestStrategy }
{ id: 'action-token-adyen', strategy: AdyenStrategy },
{ id: 'action-token-stripe', strategy: StripeStrategy },
{ id: 'action-token-test', strategy: TestStrategy },
{ id: 'action-token-wirecard', strategy: WirecardStrategy }
].forEach(({ id: actionTokenId, strategy }) => {
const threeDSecure = new ThreeDSecure({ risk, actionTokenId });

threeDSecure.whenReady(() => {
assert(threeDSecure.strategy instanceof strategy);
done();
Expand All @@ -87,23 +88,12 @@ describe('ThreeDSecure', function () {
});

it('constructs the strategy with the action token', function (done) {
const { risk, actionTokenId, sandbox } = this;

sandbox.spy(ThreeDSecure.STRATEGY_MAP, 'test');
const { risk, actionTokenId } = this;
const threeDSecure = new ThreeDSecure({ risk, actionTokenId });

threeDSecure.whenReady(() => {
assert(ThreeDSecure.STRATEGY_MAP.test.calledOnce);
assert(ThreeDSecure.STRATEGY_MAP.test.calledWithMatch({
actionToken: {
type: 'three_d_secure_action',
id: 'test-action-token-id',
gateway: { code: '1234567890', type: 'test' },
three_d_secure: {
params: { challengeType: 'challenge' }
}
}
}));
const { strategy } = threeDSecure;
assert(strategy instanceof TestStrategy);
assert.strictEqual(strategy.actionToken.id, actionTokenId);
done();
});
});
Expand Down
2 changes: 1 addition & 1 deletion test/risk/three-d-secure/strategy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'assert';
import { applyFixtures } from '../../support/fixtures';
import { testBed } from '../../support/helpers';
import ThreeDSecureStrategy from '../../../lib/recurly/risk/three-d-secure/strategy';
import actionToken from '../../server/fixtures/tokens/test-action-token-id.json';
import actionToken from '../../server/fixtures/tokens/action-token-test.json';

describe('ThreeDSecureStrategy', function () {
this.ctx.fixture = 'threeDSecure';
Expand Down
Loading

0 comments on commit ee3f1ec

Please sign in to comment.