Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Enable Braintree credit card payments via hosted fields #39

Merged
merged 23 commits into from
Feb 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
61a74be
Add Braintree client + Hosted Fields vendored JS
stewart Sep 13, 2016
fc61d55
Implement Gateway#reusable_sources
stewart Sep 14, 2016
3aff64c
Add backend JS to precompile list
stewart Sep 14, 2016
61cd77e
Add Braintree hosted fields inputs as source_form
stewart Sep 14, 2016
0b0ee0a
Add Payment form hook to create Braintree nonce
stewart Sep 16, 2016
ceee8a2
Extract hosted fields functions into separate file
Oct 20, 2016
a2e40cc
Add frontend view for Braintree credit cards
Oct 20, 2016
b0a23ce
Pass error callback function as parameter
Oct 25, 2016
db9ae29
Re-enable submit button if tokenization fails
Oct 25, 2016
d832ec8
Make hosted fields object instantiatable class
Oct 26, 2016
8074800
Move javascript out of checkout view
Oct 26, 2016
9de0c11
Configure Capybara drivers to be switchable
Oct 27, 2016
1d2f26b
Add feature spec for Braintree CC checkout
Oct 27, 2016
8fac636
Hide backend new card form by default
Oct 27, 2016
03ee385
Make Rubocop not care about gem order
adammathys Jan 31, 2017
92d83a3
Consolidate paypal_braintree checkout view
adammathys Feb 1, 2017
8480ea2
Regenerate cassettes due to new Braintree version
adammathys Feb 1, 2017
39c059b
Bump wait time for credit card specs
adammathys Feb 2, 2017
5f267f9
Add credit card to Braintree configuration
adammathys Feb 3, 2017
cf8b260
Vendor Braintree JS
adammathys Feb 3, 2017
1d6b93e
Conditionally display PayPal and CC fields
adammathys Feb 3, 2017
c52f45e
Accept any SSL protocol
adammathys Feb 3, 2017
7653cfc
Use trusty on Travis
adammathys Feb 3, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,6 @@ Metrics/ParameterLists:

Metrics/PerceivedComplexity:
Enabled: false

Bundler/OrderedGems:
Enabled: false
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
sudo: false
language: ruby
dist: trusty
cache:
directories:
- "travis_phantomjs"
Expand Down
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ end
gem 'solidus_auth_devise', '~> 1.0'

group :development, :test do
gem "chromedriver-helper"
gem "pry-rails"
gem "selenium-webdriver"
gem 'selenium-webdriver', require: false
gem 'chromedriver-helper', require: false
end

gemspec
44 changes: 42 additions & 2 deletions app/assets/javascripts/spree/backend/solidus_paypal_braintree.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
// Placeholder manifest file.
// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
//= require spree/braintree_hosted_form.js

$(function() {
var $paymentForm = $("#new_payment"),
$hostedFields = $("[data-braintree-hosted-fields]");

function onError (err) {
var msg = err.name + ": " + err.message;
show_flash("error", msg);
console.error(err);
}

// exit early if we're not looking at the New Payment form, or if no
// SolidusPaypalBraintree payment methods have been configured.
if (!$paymentForm.length || !$hostedFields.length) { return; }

$hostedFields.each(function() {
var $this = $(this),
$new = $("[name=card]", $this);

var id = $this.data("id");

var hostedFieldsInstance;

$new.on("change", function() {
var isNew = $(this).val() === "new";

function setHostedFieldsInstance(instance) {
hostedFieldsInstance = instance;
return instance;
}

if (isNew && hostedFieldsInstance == null) {
braintreeForm = new BraintreeHostedForm($paymentForm, $this, id);
braintreeForm.initializeHostedFields().
then(setHostedFieldsInstance).
then(braintreeForm.addFormHook(onError)).
fail(onError)
}
});
});
});
101 changes: 101 additions & 0 deletions app/assets/javascripts/spree/braintree_hosted_form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//= require vendor/braintree/client.js
//= require vendor/braintree/hosted_fields.js

function BraintreeHostedForm($paymentForm, $hostedFields, paymentMethodId) {
this.paymentForm = $paymentForm;
this.hostedFields = $hostedFields;
this.paymentMethodId = paymentMethodId;
}

BraintreeHostedForm.prototype.initializeHostedFields = function() {
return this.getToken().
then(this.createClient.bind(this)).
then(this.createHostedFields())
}

BraintreeHostedForm.prototype.promisify = function (fn, args, self) {
var d = $.Deferred();

fn.apply(self || this, (args || []).concat(function (err, data) {
err && d.reject(err);
d.resolve(data);
}));

return d.promise();
}

BraintreeHostedForm.prototype.getToken = function () {
var opts = {
url: "/solidus_paypal_braintree/client_token",
method: "POST",
data: {
payment_method_id: this.paymentMethodId
},
};

function onSuccess(data) {
return data.client_token;
}

return Spree.ajax(opts).then(onSuccess);
}

BraintreeHostedForm.prototype.createClient = function (token) {
var opts = { authorization: token };
return this.promisify(braintree.client.create, [opts]);
}

BraintreeHostedForm.prototype.createHostedFields = function () {
var self = this;
var id = this.paymentMethodId;

return function(client) {
var opts = {
client: client,

fields: {
number: {
selector: "#card_number" + id
},

cvv: {
selector: "#card_code" + id
},

expirationDate: {
selector: "#card_expiry" + id
}
}
};

return self.promisify(braintree.hostedFields.create, [opts]);
}
}

BraintreeHostedForm.prototype.addFormHook = function (errorCallback) {
var self = this;
var shouldSubmit = false;

function submit(payload) {
shouldSubmit = true;

$("#payment_method_nonce", self.hostedFields).val(payload.nonce);
self.paymentForm.submit();
}

return function(hostedFields) {
self.paymentForm.on("submit", function(e) {
if (self.hostedFields.is(":visible") && !shouldSubmit) {
e.preventDefault()

hostedFields.tokenize(function(err, payload) {
if (err) {
errorCallback(err);
} else {
submit(payload);
}
})
}
});
}
}
35 changes: 35 additions & 0 deletions app/assets/javascripts/spree/checkout/braintree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
$(function() {
/* This provides a default error handler for Braintree. Since we prevent
* submission if tokenization fails, we need to manually re-enable the
* submit button. */
function braintreeError (err) {
alert(err.name + ": " + err.message);

$submitButton = $("input[type='submit']", $paymentForm);
/* If we're using jquery-ujs on the frontend, it will automatically disable
* the submit button, but do so in a setTimeout here:
* https://github.com/rails/jquery-rails/blob/master/vendor/assets/javascripts/jquery_ujs.js#L517
* The only way we can re-enable it is by delaying longer than that timeout
* or stopping propagation so their submit handler doesn't run. */
if ($.rails) {
setTimeout(function () {
$.rails.enableFormElement($submitButton);
}, 100);
}
/* This reverses Spree.disableSaveOnClick() */
$submitButton.attr("disabled", false).addClass("primary").removeClass("disabled");
}

var $paymentForm = $("#checkout_form_payment"),
$hostedFields = $("[data-braintree-hosted-fields]");

$hostedFields.each(function() {
var $this = $(this);
var id = $this.data("id");

var braintreeForm = new BraintreeHostedForm($paymentForm, $this, id);
braintreeForm.initializeHostedFields().
then(braintreeForm.addFormHook(braintreeError)).
fail(braintreeError);
});
});
2 changes: 2 additions & 0 deletions app/assets/javascripts/spree/frontend/paypal_button.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//= require braintree/3.5.0/paypal

/**
* Constructor for PayPal button object
* @constructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Placeholder manifest file.
// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
//= require braintree/3.5.0/client
//= require braintree/3.5.0/data-collector
//= require spree/braintree_hosted_form.js

window.SolidusPaypalBraintree = {
APPLE_PAY_API_VERSION: 1,
Expand Down
Loading