Skip to content

Commit

Permalink
Merge pull request #317 from recurly/braintree
Browse files Browse the repository at this point in the history
Braintree-PayPal
  • Loading branch information
adeitrick authored Feb 17, 2017
2 parents f3bf4b2 + b5fd367 commit 20ae37b
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 16 deletions.
28 changes: 28 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,31 @@ errors.add('apple-pay-init-error', {
errors.add('apple-pay-payment-failure', {
message: 'Apply Pay could not charge the customer'
});

errors.add('paypal-config-missing', {
message: c => `Missing PayPal configuration option: '${c.opt}'`
});

errors.add('paypal-tokenize-error', {
message: 'An error occurred while attempting to generate the PayPal token'
});

errors.add('paypal-braintree-not-ready', {
message: 'Braintree PayPal is not yet ready to create a checkout flow'
});

errors.add('paypal-braintree-load-error', {
message: 'Braintree PayPal client libraries failed to load'
});

errors.add('paypal-braintree-api-error', {
message: 'Braintree API experienced an error'
});

errors.add('paypal-braintree-tokenize-braintree-error', {
message: 'An error occurred while attempting to generate the Braintree token'
});

errors.add('paypal-braintree-tokenize-recurly-error', {
message: 'An error occurred while attempting to generate the Recurly token'
});
4 changes: 3 additions & 1 deletion lib/recurly.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import errors from './errors';
import version from './version';
import bankAccount from './recurly/bank-account';
import {factory as ApplePay} from './recurly/apple-pay';
import {factory as PayPal, deprecatedPaypal} from './recurly/paypal';
import {Bus} from './recurly/bus';
import {Fraud} from './recurly/fraud';
import {HostedFields, FIELD_TYPES} from './recurly/hosted-fields';
Expand Down Expand Up @@ -368,7 +369,8 @@ Recurly.prototype.relay = require('./recurly/relay');
Recurly.prototype.coupon = require('./recurly/coupon');
Recurly.prototype.giftcard = giftcard;
Recurly.prototype.ApplePay = ApplePay;
Recurly.prototype.paypal = require('./recurly/paypal');
Recurly.prototype.PayPal = PayPal;
Recurly.prototype.paypal = deprecatedPaypal;
Recurly.prototype.plan = require('./recurly/plan');
Recurly.prototype.tax = require('./recurly/tax');
Recurly.prototype.token = require('./recurly/token');
Expand Down
15 changes: 0 additions & 15 deletions lib/recurly/paypal.js

This file was deleted.

99 changes: 99 additions & 0 deletions lib/recurly/paypal/braintree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import loadScript from 'load-script';
import Emitter from 'component-emitter';
import errors from '../../errors';

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

/**
* Braintree-specific PayPal handler
*
* TODO: make inherit from PayPal instead of Emitter to consolidate error handler and init
*/

export class BraintreePayPal extends Emitter {
constructor (options) {
super();
this.ready = false;
this.config = {};
this.configure(options);
}

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

load () {
debug('loading Braintree libraries');
loadScript('https://js.braintreegateway.com/web/3.6.3/js/client.min.js', () => {
loadScript('https://js.braintreegateway.com/web/3.6.3/js/paypal.min.js', () => {
this.initialize();
});
});
}

initialize () {
if (!global.braintree) return this.error('paypal-braintree-load-error');
debug('Initializing Braintree client');

const authorization = this.config.clientAuthorization;

braintree.client.create({ authorization }, (error, client) => {
if (error) return this.error('paypal-braintree-api-error', { error });
debug('Braintree client created');
braintree.paypal.create({ client }, (error, paypal) => {
if (error) return this.error('paypal-braintree-api-error', { error });
debug('PayPal client created');
this.paypal = paypal;
this.ready = true;
});
});
}

/**
* Starts the PayPal flow
* > must be on the call chain with a user interaction (click, touch) on it
*
* @emit 'paypal-braintree-tokenize-braintree-error'
* @emit 'paypal-braintree-tokenize-recurly-error'
* @emit 'token'
* @emit 'cancel'
*/
start () {
if (!this.ready) return this.error('paypal-braintree-not-ready');

// Tokenize with Braintree
this.paypal.tokenize({ flow: 'vault' }, (error, payload) => {
if (error) {
if (error.code === 'PAYPAL_POPUP_CLOSED') return this.emit('cancel');
return this.error('paypal-braintree-tokenize-braintree-error', { error });
}

debug('Token payload received', payload);

// Tokenize with Recurly
this.recurly.request('post', '/paypal/token', { payload }, (error, token) => {
if (error) return this.error('paypal-braintree-tokenize-recurly-error', { error });
this.emit('token', token);
});
});
}

/**
* Creates and emits a RecurlyError
*
* @param {...Mixed} params to be passed to the Recurlyerror factory
* @return {RecurlyError}
* @emit 'error'
* @private
*/
error (...params) {
let err = params[0] instanceof Error ? params[0] : errors(...params);
this.emit('error', err);
return err;
}
}
61 changes: 61 additions & 0 deletions lib/recurly/paypal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Emitter from 'component-emitter';
import {BraintreePayPal} from './braintree';

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

/**
* Instantiation factory
*
* @param {Object} options
* @param {Function} done
* @return {PayPal}
*/
export function factory (options, done) {
options = Object.assign({}, options, { recurly: this });
if (options.braintree) return new BraintreePayPal(options);
return new PayPal(options);
};

export class PayPal extends Emitter {
constructor (options) {
super();
this.recurly = options.recurly;
}

/**
* Pass-through for now to deprecated paypal opener method
*/
start (data) {
deprecatedPaypal.apply(this.recurly, data, (err, token) => {
if (err) this.error('paypal-tokenize-error', { err });
else this.emit('token', token);
});
}

/**
* Creates and emits a RecurlyError
*
* @param {...Mixed} params to be passed to the Recurlyerror factory
* @return {RecurlyError}
* @emit 'error'
* @private
*/
error (...params) {
let err = params[0] instanceof Error ? params[0] : errors(...params);
this.emit('error', err);
return err;
}
}

/**
* Deprecated Paypal mixin.
*
* @deprecated
* @param {Object} data
* @param {Function} done callback
*/

export function deprecatedPaypal (data, done) {
debug('start');
this.open('/paypal/start', data, done);
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"debug": "2.2.0",
"json-component": "0.0.1",
"jsonp": "0.0.4",
"load-script": "^1.0.0",
"lodash.map": "^4.6.0",
"lodash.merge": "*",
"lodash.omit": "*",
Expand Down

0 comments on commit 20ae37b

Please sign in to comment.