-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
98ca564
commit f9806fe
Showing
9 changed files
with
6,540 additions
and
8,084 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import Emitter from 'component-emitter'; | ||
import { BraintreeStrategy } from './strategy/braintree'; | ||
|
||
/** | ||
* Venmo instantiation factory | ||
* | ||
* @param {Object} options | ||
* @return {Venmo} | ||
*/ | ||
export function factory (options) { | ||
options = Object.assign({}, options, { recurly: this }); | ||
return new Venmo(options); | ||
} | ||
|
||
const DEFERRED_EVENTS = [ | ||
'ready', | ||
'token', | ||
'error', | ||
'cancel' | ||
]; | ||
|
||
/** | ||
* Venmo strategy interface | ||
*/ | ||
class Venmo extends Emitter { | ||
constructor (options) { | ||
super(); | ||
this.isReady = false; | ||
this.options = options; | ||
|
||
this.once('ready', () => this.isReady = true); | ||
|
||
this.strategy = new BraintreeStrategy(options); | ||
|
||
this.bindStrategy(); | ||
} | ||
|
||
ready (done) { | ||
if (this.isReady) done(); | ||
else this.once('ready', done); | ||
} | ||
|
||
start (...args) { | ||
return this.strategy.start(...args); | ||
} | ||
|
||
destroy () { | ||
const { strategy } = this; | ||
if (strategy) { | ||
strategy.destroy(); | ||
delete this.strategy; | ||
} | ||
this.off(); | ||
} | ||
|
||
/** | ||
* Binds external interface events to those on the strategy | ||
* | ||
* @private | ||
*/ | ||
bindStrategy () { | ||
DEFERRED_EVENTS.forEach(ev => this.strategy.on(ev, this.emit.bind(this, ev))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import loadScript from 'load-script'; | ||
import after from '../../../util/after'; | ||
import { VenmoStrategy } from './index'; | ||
import { normalize } from '../../../util/normalize'; | ||
|
||
export const BRAINTREE_CLIENT_VERSION = '3.50.0'; | ||
|
||
const debug = require('debug')('recurly:paypal:strategy:braintree'); | ||
|
||
/** | ||
* Braintree-specific Venmo handler | ||
*/ | ||
|
||
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; | ||
} | ||
|
||
/** | ||
* Loads Braintree client and modules | ||
* | ||
* @todo semver client detection | ||
*/ | ||
load ({ form }) { | ||
debug('loading Braintree libraries'); | ||
this.form = 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); | ||
} | ||
|
||
/** | ||
* Initializes a Braintree client, device data collection module, and venmo client | ||
*/ | ||
initialize () { | ||
debug('Initializing Braintree client'); | ||
|
||
const authorization = this.config.clientAuthorization; | ||
const braintree = window.braintree; | ||
|
||
braintree.client.create({ authorization }, (error, client) => { | ||
if (error) return this.fail('venmo-braintree-api-error', { cause: error }); | ||
debug('Braintree client created'); | ||
braintree.venmo.create({ client }, (error, venmo) => { | ||
if (error) return this.fail('venmo-braintree-api-error', { cause: error }); | ||
debug('Venmo client created'); | ||
this.venmo = venmo; | ||
this.emit('ready'); | ||
}); | ||
}); | ||
} | ||
|
||
handleVenmoError (err) { | ||
if (err.code === 'VENMO_CANCELED') { | ||
console.log('App is not available or user aborted payment flow'); | ||
} else if (err.code === 'VENMO_APP_CANCELED') { | ||
if (err.code === 'VENMO_POPUP_CLOSED') | ||
console.log('User canceled payment flow'); | ||
} else { | ||
console.error('An error occurred:', err.message); | ||
} | ||
|
||
this.emit('cancel'); | ||
return this.error('venmo-braintree-tokenize-braintree-error', { cause: err }); | ||
} | ||
|
||
handleVenmoSuccess (payload) { | ||
const nameData = normalize(this.form, ['first_name', 'last_name']); | ||
this.recurly.request.post({ | ||
route: '/venmo/token', | ||
data: { type: 'braintree', payload: { ...payload, ...nameData } }, | ||
done: (error, token) => { | ||
if (error) return this.error('venmo-braintree-tokenize-recurly-error', { cause: error }); | ||
this.emit('token', token); | ||
} | ||
}); | ||
} | ||
|
||
start () { | ||
// Tokenize with Braintree | ||
this.venmo.tokenize() | ||
.then(this.handleVenmoSuccess.bind(this)) | ||
.catch(this.handleVenmoError.bind(this)); | ||
} | ||
|
||
destroy () { | ||
if (this.close) { | ||
this.close(); | ||
} | ||
this.off(); | ||
} | ||
|
||
braintreeClientAvailable (module) { | ||
const bt = window.braintree; | ||
return bt && bt.client && bt.client.VERSION === BRAINTREE_CLIENT_VERSION && (module ? module in bt : true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import Emitter from 'component-emitter'; | ||
import { Recurly } from '../../../recurly'; | ||
import errors from '../../errors'; | ||
|
||
const debug = require('debug')('recurly:paypal:strategy'); | ||
|
||
/** | ||
* Venmo base interface strategy | ||
* | ||
* @abstract | ||
*/ | ||
export class VenmoStrategy extends Emitter { | ||
constructor (options) { | ||
super(); | ||
this.isReady = false; | ||
this.config = {}; | ||
|
||
this.once('ready', () => this.isReady = true); | ||
|
||
this.configure(options[0]); | ||
} | ||
|
||
ready (done) { | ||
if (this.isReady) done(); | ||
else this.once('ready', done); | ||
} | ||
|
||
configure (options) { | ||
if (!(options.recurly instanceof Recurly)) throw this.error('venmo-factory-only'); | ||
this.recurly = options.recurly; | ||
} | ||
|
||
initialize () { | ||
debug("Method 'initialize' not implemented"); | ||
} | ||
|
||
/** | ||
* Starts the PayPal flow | ||
* > must be on the call chain with a user interaction (click, touch) on it | ||
*/ | ||
start () { | ||
debug("Method 'start' not implemented"); | ||
} | ||
|
||
cancel () { | ||
this.emit('cancel'); | ||
} | ||
|
||
/** | ||
* Registers or immediately invokes a failure handler | ||
* | ||
* @param {Function} done Failure handler | ||
*/ | ||
onFail (done) { | ||
if (this.failure) done(); | ||
else this.once('fail', done); | ||
} | ||
|
||
/** | ||
* Logs and announces a failure to initialize a strategy | ||
* | ||
* @private | ||
* @param {String} reason | ||
* @param {Object} [options] | ||
*/ | ||
fail (reason, options) { | ||
if (this.failure) return; | ||
debug('Failure scenario encountered', reason, options); | ||
const failure = this.failure = this.error(reason, options); | ||
this.emit('fail', failure); | ||
} | ||
|
||
/** | ||
* Creates and emits a RecurlyError | ||
* | ||
* @protected | ||
* @param {...Mixed} params to be passed to the Recurlyerror factory | ||
* @return {RecurlyError} | ||
* @emit 'error' | ||
*/ | ||
error (...params) { | ||
let err = params[0] instanceof Error ? params[0] : errors(...params); | ||
this.emit('error', err); | ||
return err; | ||
} | ||
|
||
/** | ||
* Updates price information from a Pricing instance | ||
* | ||
* @private | ||
*/ | ||
updatePriceFromPricing () { | ||
this.config.display.amount = this.pricing.totalNow; | ||
this.config.display.currency = this.pricing.currencyCode; | ||
} | ||
} |
Oops, something went wrong.