Skip to content

Commit

Permalink
Merge pull request #891 from recurly/consolidate-braintree
Browse files Browse the repository at this point in the history
chore: extract loading braintree libraries to util/braintree
  • Loading branch information
chrissrogers authored Aug 14, 2024
2 parents 1ea4ec5 + b10f978 commit e0f595f
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 206 deletions.
1 change: 0 additions & 1 deletion lib/const/gateway-constants.js

This file was deleted.

29 changes: 2 additions & 27 deletions lib/recurly/apple-pay/apple-pay.braintree.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
import Promise from 'promise';
import { ApplePay } from './apple-pay';
import loadScriptPromise from '../../util/load-script-promise';
import Debug from 'debug';
import { BRAINTREE_CLIENT_VERSION } from '../../const/gateway-constants';
import BraintreeLoader from '../../util/braintree-loader';

const debug = Debug('recurly:apple-pay:braintree');

const LIBS = {
client: 'client',
applePay: 'apple-pay',
dataCollector: 'data-collector',
};

const loadBraintree = (...libs) => {
const loadLib = lib => {
const isLibPresent = window.braintree?.client?.VERSION === BRAINTREE_CLIENT_VERSION &&
lib in window.braintree;

return isLibPresent
? Promise.resolve()
: loadScriptPromise(ApplePayBraintree.libUrl(lib));
};

return loadLib('client')
.then(() => Promise.all(libs.map(loadLib)));
};

export class ApplePayBraintree extends ApplePay {
static libUrl (lib) {
return `https://js.braintreegateway.com/web/${BRAINTREE_CLIENT_VERSION}/js/${LIBS[lib]}.min.js`;
}

configure (options) {
debug('Initializing client');

const authorization = options.braintree.clientAuthorization;
if (options.braintree.displayName) this.displayName = options.braintree.displayName;
else this.displayName = 'My Store';

loadBraintree('applePay', 'dataCollector')
BraintreeLoader.loadModules('applePay', 'dataCollector')
.then(() => window.braintree.client.create({ authorization }))
.then(client => Promise.all([
window.braintree.dataCollector.create({ client }),
Expand Down
44 changes: 4 additions & 40 deletions lib/recurly/paypal/strategy/braintree.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import loadScript from 'load-script';
import after from '../../../util/after';
import BraintreeLoader from '../../../util/braintree-loader';
import { PayPalStrategy } from './index';
import { BRAINTREE_CLIENT_VERSION } from '../../../const/gateway-constants';

const debug = require('debug')('recurly:paypal:strategy:braintree');

Expand All @@ -10,45 +8,16 @@ const debug = require('debug')('recurly:paypal:strategy:braintree');
*/

export class BraintreeStrategy extends PayPalStrategy {
constructor (...args) {
super(...args);
this.load();
}

configure (options) {
super.configure(options);
if (!options.braintree || !options.braintree.clientAuthorization) {
throw this.error('paypal-config-missing', { opt: 'braintree.clientAuthorization' });
}
this.config.clientAuthorization = options.braintree.clientAuthorization;
}

/**
* Loads Braintree client and modules
*
* @todo semver client detection
*/
load () {
debug('loading Braintree libraries');

const part = after(2, () => this.initialize());
const get = (lib, done = () => {}) => {
const uri = `https://js.braintreegateway.com/web/${BRAINTREE_CLIENT_VERSION}/js/${lib}.min.js`;
loadScript(uri, error => {
if (error) this.error('paypal-load-error', { cause: error });
else done();
});
};

const modules = () => {
if (this.braintreeClientAvailable('paypal')) part();
else get('paypal', part);
if (this.braintreeClientAvailable('dataCollector')) part();
else get('data-collector', part);
};

if (this.braintreeClientAvailable()) modules();
else get('client', modules);
BraintreeLoader.loadModules('paypal', 'dataCollector')
.catch(cause => this.error('paypal-load-error', { cause }))
.then(() => this.initialize());
}

/**
Expand Down Expand Up @@ -113,9 +82,4 @@ export class BraintreeStrategy extends PayPalStrategy {
}
this.off();
}

braintreeClientAvailable (module) {
const bt = window.braintree;
return bt && bt.client && bt.client.VERSION === BRAINTREE_CLIENT_VERSION && (module ? module in bt : true);
}
}
30 changes: 5 additions & 25 deletions lib/recurly/risk/three-d-secure/strategy/braintree.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import loadScript from 'load-script';
import Promise from 'promise';
import BraintreeLoader from '../../../../util/braintree-loader';
import ThreeDSecureStrategy from './strategy';
import { BRAINTREE_CLIENT_VERSION } from '../../../../const/gateway-constants';

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

export default class BraintreeStrategy extends ThreeDSecureStrategy {

static strategyName = 'braintree_blue';

loadBraintreeLibraries () {
return BraintreeLoader.loadModules('threeDSecure');
}

constructor (...args) {
super(...args);

debug('loading braintree libraries');
this.loadBraintreeLibraries()
.catch(cause => this.threeDSecure.error('3ds-vendor-load-error', { vendor: 'Braintree', cause }))
.then(() => {
this.braintree = window.braintree;
debug('Braintree checkout instance created', this.braintree);
this.markReady();
});
}
Expand Down Expand Up @@ -97,24 +97,4 @@ export default class BraintreeStrategy extends ThreeDSecureStrategy {
.catch(cause => this.threeDSecure.error('3ds-auth-error', { cause }));
});
}

urlForResource (type) {
return `https://js.braintreegateway.com/web/${BRAINTREE_CLIENT_VERSION}/js/${type}.min.js`;
}

/**
* Loads Braintree library dependency
*/
loadBraintreeLibraries () {
return new Promise((resolve, reject) => {
if (window.braintree && window.braintree.client && window.braintree.threeDSecure) return resolve();
loadScript(this.urlForResource('client'), error => {
if (error) reject(error);
else loadScript(this.urlForResource('three-d-secure'), error => {
if (error) reject(error);
else resolve();
});
});
});
}
}
46 changes: 6 additions & 40 deletions lib/recurly/venmo/strategy/braintree.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import loadScript from 'load-script';
import after from '../../../util/after';
import BraintreeLoader from '../../../util/braintree-loader';
import { VenmoStrategy } from './index';
import { normalize } from '../../../util/normalize';
import { BRAINTREE_CLIENT_VERSION } from '../../../const/gateway-constants';

const debug = require('debug')('recurly:venmo:strategy:braintree');

Expand All @@ -11,47 +9,20 @@ const debug = require('debug')('recurly:venmo:strategy:braintree');
*/

export class BraintreeStrategy extends VenmoStrategy {
constructor (...args) {
super(args);
this.load(args[0]);
}

configure (options) {
super.configure(options);

if (!options.braintree || !options.braintree.clientAuthorization) {
throw this.error('venmo-config-missing', { opt: 'braintree.clientAuthorization' });
}
this.config.clientAuthorization = options.braintree.clientAuthorization;
this.config.allowDesktopWebLogin = options.braintree.webAuthentication ? options.braintree.webAuthentication : false;
}

/**
* Loads Braintree client and modules
*
* @todo semver client detection
*/
load ({ form }) {
debug('loading Braintree libraries');
this.form = form;
this.form = options.form;

const part = after(2, () => this.initialize());
const get = (lib, done = () => {}) => {
const uri = `https://js.braintreegateway.com/web/${BRAINTREE_CLIENT_VERSION}/js/${lib}.min.js`;
loadScript(uri, error => {
if (error) this.error('venmo-load-error', { cause: error });
else done();
});
};

const modules = () => {
if (this.braintreeClientAvailable('venmo')) part();
else get('venmo', part);
if (this.braintreeClientAvailable('dataCollector')) part();
else get('data-collector', part);
};

if (this.braintreeClientAvailable()) modules();
else get('client', modules);
BraintreeLoader.loadModules('venmo', 'dataCollector')
.catch(cause => this.error('venmo-load-error', { cause }))
.then(() => this.initialize());
}

/**
Expand Down Expand Up @@ -124,9 +95,4 @@ export class BraintreeStrategy extends VenmoStrategy {
}
this.off();
}

braintreeClientAvailable (module) {
const bt = window.braintree;
return bt && bt.client && bt.client.VERSION === BRAINTREE_CLIENT_VERSION && (module ? module in bt : true);
}
}
2 changes: 1 addition & 1 deletion lib/recurly/venmo/strategy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class VenmoStrategy extends Emitter {

this.once('ready', () => this.isReady = true);

this.configure(options[0]);
this.configure(options);
}

ready (done) {
Expand Down
11 changes: 0 additions & 11 deletions lib/util/after.js

This file was deleted.

37 changes: 37 additions & 0 deletions lib/util/braintree-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import loadScriptPromise from './load-script-promise';
import Debug from 'debug';

const debug = Debug('recurly:braintree');

const BRAINTREE_CLIENT_VERSION = '3.101.0';

const MOD_TO_LIB = {
'dataCollector': 'data-collector',
'applePay': 'apple-pay',
'googlePayment': 'google-payment',
'threeDSecure': 'three-d-secure',
};

const libUrl = (mod) => {
const btMod = MOD_TO_LIB[mod] || mod;
return `https://js.braintreegateway.com/web/${BRAINTREE_CLIENT_VERSION}/js/${btMod}.min.js`;
};

const loadModuleScript = (mod) => {
const isModulePresent = window.braintree?.client?.VERSION === BRAINTREE_CLIENT_VERSION && mod in window.braintree;

return isModulePresent
? Promise.resolve()
: loadScriptPromise(libUrl(mod));
};

export default {
BRAINTREE_CLIENT_VERSION,

loadModules: (...modules) => {
debug('loading Braintree client modules', modules);

return loadModuleScript('client')
.then(() => Promise.all(modules.map(loadModuleScript)));
},
};
54 changes: 7 additions & 47 deletions test/unit/apple-pay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import omit from 'lodash.omit';
import Emitter from 'component-emitter';
import Promise from 'promise';
import { initRecurly, nextTick } from './support/helpers';
import BraintreeLoader from '../../lib/util/braintree-loader';
import { ApplePayBraintree } from '../../lib/recurly/apple-pay/apple-pay.braintree';
import filterSupportedNetworks from '../../lib/recurly/apple-pay/util/filter-supported-networks';

Expand Down Expand Up @@ -78,9 +79,7 @@ const getBraintreeStub = () => ({
},
});

const maybeDescribe = 'ApplePaySession' in window ? describe : describe.skip;

maybeDescribe('ApplePay', function () {
describe('ApplePay', function () {
beforeEach(function () {
this.sandbox = sinon.createSandbox();
window.ApplePaySession = ApplePaySessionStub;
Expand Down Expand Up @@ -719,58 +718,19 @@ function applePayTest (integrationType) {
describe('when the libs are not loaded', function () {
beforeEach(function () {
delete window.braintree;
this.sandbox.stub(ApplePayBraintree, 'libUrl').returns('/api/mock-200');
this.sandbox.stub(BraintreeLoader, 'loadModules').rejects('boom');
});

it('load the libs', function (done) {
const applePay = this.recurly.ApplePay(validOpts);
applePay.on('error', ensureDone(done, () => {
assert.equal(ApplePayBraintree.libUrl.callCount, 3);
assert.equal(ApplePayBraintree.libUrl.getCall(0).args[0], 'client');
assert.equal(ApplePayBraintree.libUrl.getCall(1).args[0], 'applePay');
assert.equal(ApplePayBraintree.libUrl.getCall(2).args[0], 'dataCollector');
applePay.on('error', ensureDone(done, (err) => {
assert(BraintreeLoader.loadModules.calledWith('applePay', 'dataCollector'));
assert.equal(err, applePay.initError);
assertInitError(applePay, 'apple-pay-init-error');
}));
});
});

const requiredBraintreeLibs = ['client', 'dataCollector', 'applePay'];
requiredBraintreeLibs.forEach(requiredLib => {
describe(`when failed to load the braintree ${requiredLib} lib`, function () {
beforeEach(function () {
delete window.braintree;
this.sandbox.stub(ApplePayBraintree, 'libUrl').withArgs(requiredLib).returns('/api/mock-404');
});

it('register an initialization error', function (done) {
const applePay = this.recurly.ApplePay(validOpts);

applePay.on('error', (err) => {
nextTick(ensureDone(done, () => {
assert.equal(err, applePay.initError);
assertInitError(applePay, 'apple-pay-init-error');
}));
});
});
});

describe(`when failed to create the ${requiredLib} instance`, function () {
beforeEach(function () {
window.braintree[requiredLib].create = sinon.stub().rejects('error');
});

it('register an initialization error', function (done) {
const applePay = this.recurly.ApplePay(validOpts);

applePay.on('error', (err) => {
nextTick(ensureDone(done, () => {
assert.equal(err, applePay.initError);
assertInitError(applePay, 'apple-pay-init-error');
}));
});
});
});
});

it('assigns the braintree configuration', function (done) {
const applePay = this.recurly.ApplePay(validOpts);

Expand Down
Loading

0 comments on commit e0f595f

Please sign in to comment.