recurly/recurly-js

View on GitHub
lib/recurly/venmo/strategy/braintree.js

Summary

Maintainability
C
1 day
Test Coverage
import loadScript from 'load-script';
import after from '../../../util/after';
import { VenmoStrategy } from './index';
import { normalize } from '../../../util/normalize';
import { BRAINTREE_CLIENT_VERSION } from '../../../const/gateway-constants';

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;
    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;

    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;
    const allowDesktopWebLogin = this.config.allowDesktopWebLogin;

    braintree.client.create({ authorization }, (error, client) => {
      if (error) return this.fail('venmo-braintree-api-error', { cause: error });
      debug('Braintree client created');
      braintree.dataCollector.create({ client, paypal: true }, (error, collector) => {
        if (error) return this.fail('venmo-braintree-api-error', { cause: error });
        debug('Device data collector created');
        this.deviceFingerprint = collector.deviceData;
        braintree.venmo.create({
          client,
          allowDesktop: true,
          allowDesktopWebLogin:  allowDesktopWebLogin,
          paymentMethodUsage: 'multi_use',
          collectCustomerBillingAddress: true,
          collectCustomerShippingAddress: true,
        }, (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) {
    debug('Venmo error', err);
    this.emit('cancel');
    return this.error('venmo-braintree-tokenize-braintree-error', { cause: err });
  }

  handleVenmoSuccess (payload) {
    const nameData = normalize(this.form, ['first_name', 'last_name']);

    if (this.deviceFingerprint) {
      payload.deviceFingerprint = this.deviceFingerprint;
    }

    this.recurly.request.post({
      route: '/venmo/token',
      data: { type: 'braintree', payload: { ...payload, values: nameData.values } },
      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);
  }
}