solidus_paypal_braintree 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +196 -0
  4. data/Rakefile +30 -0
  5. data/app/assets/javascripts/spree/backend/solidus_paypal_braintree.js +66 -0
  6. data/app/assets/javascripts/spree/braintree_hosted_form.js +98 -0
  7. data/app/assets/javascripts/spree/checkout/braintree.js +60 -0
  8. data/app/assets/javascripts/spree/frontend/paypal_button.js +182 -0
  9. data/app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js +188 -0
  10. data/app/assets/stylesheets/spree/backend/solidus_paypal_braintree.scss +28 -0
  11. data/app/assets/stylesheets/spree/frontend/solidus_paypal_braintree.css +16 -0
  12. data/app/controllers/solidus_paypal_braintree/client_tokens_controller.rb +21 -0
  13. data/app/helpers/braintree_admin_helper.rb +18 -0
  14. data/app/models/application_record.rb +3 -0
  15. data/app/models/solidus_paypal_braintree/configuration.rb +5 -0
  16. data/app/models/solidus_paypal_braintree/customer.rb +4 -0
  17. data/app/models/solidus_paypal_braintree/gateway.rb +323 -0
  18. data/app/models/solidus_paypal_braintree/response.rb +52 -0
  19. data/app/models/solidus_paypal_braintree/source.rb +73 -0
  20. data/app/models/solidus_paypal_braintree/transaction.rb +30 -0
  21. data/app/models/solidus_paypal_braintree/transaction_address.rb +66 -0
  22. data/app/models/solidus_paypal_braintree/transaction_import.rb +92 -0
  23. data/app/models/spree/store_decorator.rb +11 -0
  24. data/app/overrides/admin_navigation_menu.rb +6 -0
  25. data/app/views/spree/shared/_braintree_hosted_fields.html.erb +26 -0
  26. data/config/initializers/braintree.rb +1 -0
  27. data/config/locales/en.yml +30 -0
  28. data/config/routes.rb +12 -0
  29. data/db/migrate/20160830061749_create_solidus_paypal_braintree_sources.rb +16 -0
  30. data/db/migrate/20160906201711_create_solidus_paypal_braintree_customers.rb +11 -0
  31. data/db/migrate/20161114231422_create_solidus_paypal_braintree_configurations.rb +11 -0
  32. data/db/migrate/20161125172005_add_braintree_configuration_to_stores.rb +9 -0
  33. data/db/migrate/20170203191030_add_credit_card_to_braintree_configuration.rb +6 -0
  34. data/db/migrate/20170505193712_add_null_constraint_to_sources.rb +30 -0
  35. data/db/migrate/20170508085402_add_not_null_constraint_to_sources_payment_type.rb +11 -0
  36. data/lib/controllers/backend/solidus_paypal_braintree/configurations_controller.rb +30 -0
  37. data/lib/controllers/frontend/solidus_paypal_braintree/checkouts_controller.rb +27 -0
  38. data/lib/controllers/frontend/solidus_paypal_braintree/transactions_controller.rb +61 -0
  39. data/lib/generators/solidus_paypal_braintree/install/install_generator.rb +37 -0
  40. data/lib/solidus_paypal_braintree.rb +10 -0
  41. data/lib/solidus_paypal_braintree/country_mapper.rb +35 -0
  42. data/lib/solidus_paypal_braintree/engine.rb +53 -0
  43. data/lib/solidus_paypal_braintree/factories.rb +18 -0
  44. data/lib/solidus_paypal_braintree/version.rb +3 -0
  45. data/lib/views/backend/solidus_paypal_braintree/configurations/_admin_tab.html.erb +3 -0
  46. data/lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb +30 -0
  47. data/lib/views/backend/spree/admin/payments/source_forms/_paypal_braintree.html.erb +16 -0
  48. data/lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb +34 -0
  49. data/lib/views/backend_v1.2/spree/admin/payments/source_forms/_paypal_braintree.html.erb +16 -0
  50. data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +52 -0
  51. metadata +350 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 56c8047e148da21f23e2c7764b03fbfa7091c9ca
4
+ data.tar.gz: 4fd0ea732eecabaa4781fdd5bba6e8ae16a22bb8
5
+ SHA512:
6
+ metadata.gz: 4346d0ca96f967828853a548addef236066b9c87b738326da3b72b05570ac37a2e57787d1d7b517d90a33dd0bb7f79f2f45a9399ab996bc50ab027bef559bcc8
7
+ data.tar.gz: 844c939eb60eae893152b9cd3131307eb0e1edf79e6d097955bc325a77c7a5fc0368a4042f0f3d957a8ace3616843004bd2bb37ccff93e80cb966b8b99b57430
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2016 Stembolt
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,196 @@
1
+ SolidusPaypalBraintree
2
+ ======================
3
+
4
+ [![Build Status](https://travis-ci.org/solidusio/solidus_paypal_braintree.svg?branch=master)](https://travis-ci.org/solidusio/solidus_paypal_braintree)
5
+
6
+ `solidus_paypal_braintree` is an extension that adds support for using [Braintree](https://www.braintreepayments.com) as a payment source in your [Solidus](https://solidus.io/) store. It supports Apple Pay, PayPal, and credit card transactions.
7
+
8
+ Installation
9
+ ------------
10
+
11
+ Add solidus_paypal_braintree to your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'solidus_paypal_braintree', github: 'solidusio/solidus_paypal_braintree', branch: :master
15
+ ```
16
+
17
+ Bundle your dependencies and run the installation generator:
18
+
19
+ ```shell
20
+ bundle
21
+ bundle exec rails g solidus_paypal_braintree:install
22
+ ```
23
+
24
+ ## Basic Setup
25
+
26
+ ### Retrieve Braintree account details
27
+ You'll need the following account details:
28
+ - `Merchant ID`
29
+ - `Public key`
30
+ - `Private key`
31
+
32
+ These values can be obtained by logging in to your Braintree account, going
33
+ to `Account -> My User` and clicking `View Authorizations` in the **API Keys,
34
+ Tokenization Keys, Encryption Keys** section.
35
+
36
+ ### Create a new payment method
37
+ Payment methods can accept preferences either directly entered in admin, or from a static source in code. For most projects we recommend using a static source, so that sensitive account credentials are not stored in the database.
38
+
39
+ 1. Set static preferences in an initializer
40
+ ```ruby
41
+ # config/initializers/spree.rb
42
+ Spree::Config.configure do |config|
43
+ config.static_model_preferences.add(
44
+ SolidusPaypalBraintree::Gateway,
45
+ 'braintree_credentials', {
46
+ environment: Rails.env.production? ? 'production' : 'sandbox',
47
+ merchant_id: ENV['BRAINTREE_MERCHANT_ID'],
48
+ public_key: ENV['BRAINTREE_PUBLIC_KEY'],
49
+ private_key: ENV['BRAINTREE_PRIVATE_KEY']
50
+ }
51
+ )
52
+ end
53
+ ```
54
+ Other optional preferences are discussed below.
55
+ 2. Visit `/admin/payment_methods/new`
56
+ 3. Set `provider` to SolidusPaypalBraintree::Gateway
57
+ 4. Click "Save"
58
+ 5. Choose `braintree_credentials` from the `Preference Source` select
59
+ 6. Click `Update` to save
60
+
61
+ Alternatively, create a payment method from the Rails console with:
62
+ ```ruby
63
+ SolidusPaypalBraintree::Gateway.new(
64
+ name: "Braintree",
65
+ preference_source: "braintree_credentials"
66
+ ).save
67
+ ```
68
+
69
+ ### Configure payment types
70
+ Your payment method can accept payments in three ways: through Paypal, through ApplePay, or with credit card details entered directly by the customer. By default all are disabled for all your site's stores.
71
+ 1. Visit /solidus_paypal_braintree/configurations/list
72
+ 2. Check the payment types you'd like to accept. If your site has multiple stores, there'll be a set of checkboxes for each.
73
+ 3. Click `Save changes` to save
74
+
75
+ Or from the console:
76
+ ```ruby
77
+ Spree::Store.all.each do |store|
78
+ store.create_braintree_configuration(
79
+ credit_card: true,
80
+ paypal: true,
81
+ apple_pay: true
82
+ )
83
+ end
84
+ ```
85
+
86
+ 3. If your site uses an unmodified `solidus_frontend`, it should now be ready to take credit card payments (if you've enabled that payment type). Paypal and ApplePay require some further configuration, discussed below.
87
+
88
+ Apple Pay
89
+ ---------
90
+
91
+ ### Setup
92
+ Braintree has some [excellent documentation](https://developers.braintreepayments.com/guides/apple-pay/configuration/javascript/v3) on what you'll need to do to get Apple Pay up and running.
93
+
94
+ In order to make everything a little simpler, this extension includes some client-side code to get you started. Specifically, it provides some wrappers to help with the initialization of an Apple Pay session. The following is a relatively bare-bones implementation:
95
+ ```javascript
96
+ var applePayButton = document.getElementById('your-apple-pay-button');
97
+ window.SolidusPaypalBraintree.fetchToken(function(clientToken) {
98
+ window.SolidusPaypalBraintree.initialize(clientToken, function(braintreeClient) {
99
+ window.SolidusPaypalBraintree.setupApplePay(braintreeClient, "YOUR-MERCHANT-ID", funtion(applePayInstance) {
100
+ applePayButton.addEventListener('click', function() { beginApplePayCheckout(applePayInstance) });
101
+ }
102
+ }
103
+ }
104
+
105
+ beginApplePayCheckout = function(applePayInstance) {
106
+ window.SolidusPaypalBraintree.initializeApplePaySession({
107
+ applePayInstance: applePayInstance,
108
+ storeName: 'Your Store Name',
109
+ currentUserEmail: Spree.current_email,
110
+ paymentMethodId: Spree.braintreePaymentMethodId,
111
+ }, (session) => {
112
+ // Add in your logic for onshippingcontactselected and onshippingmethodselected.
113
+ }
114
+ };
115
+ ```
116
+
117
+ For additional information checkout the [Apple's documentation](https://developer.apple.com/reference/applepayjs/) and [Braintree's documentation](https://developers.braintreepayments.com/guides/apple-pay/client-side/javascript/v3).
118
+
119
+ ### Development
120
+ Developing with Apple Pay has a few gotchas. First and foremost, you'll need to ensure you have access to a device running iOS 10+. (I've heard there's also been progress on adding support to the Simulator.)
121
+
122
+ Next, you'll need an Apple Pay sandbox account. You can check out Apple's [documentation](https://developer.apple.com/support/apple-pay-sandbox/) for additional help in performing this step.
123
+
124
+ Finally, Apple Pay requires the site to be served via HTTPS. I recommend setting up a proxy server to help solve this. There are [lots of guides](https://www.google.ca/search?q=nginx+reverse+proxy+ssl+localhost) on how this can be achieved.
125
+
126
+ PayPal
127
+ ------
128
+
129
+ A default checkout view is provided that will display PayPal as a payment option.
130
+ It will only be displayed if the `SolidusPaypalBraintree::Gateway` payment
131
+ method is configured to display on the frontend and PayPal is enabled in the
132
+ store's configuration.
133
+
134
+ The checkout view
135
+ [initializes the PayPal button](/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb)
136
+ using the
137
+ [vault flow](https://developers.braintreepayments.com/guides/paypal/overview/javascript/v3),
138
+ which allows the source to be reused.
139
+
140
+ If you are creating your own checkout view or would like to customize the
141
+ [options that get passed to tokenize](https://braintree.github.io/braintree-web/3.6.3/PayPal.html#tokenize)
142
+ , you can initialize your own using the `PaypalButton` JS object:
143
+
144
+ ```javascript
145
+ var button = new PaypalButton(document.querySelector("#your-button-id"));
146
+
147
+ button.initialize({
148
+ // your configuration options here
149
+ });
150
+ ```
151
+
152
+ After successful tokenization, a callback function is invoked that submits the
153
+ transaction via AJAX and advances the order to confirm. It is possible to provide
154
+ your own callback function to customize the behaviour after tokenize as follows:
155
+
156
+ ```javascript
157
+ var button = new PaypalButton(document.querySelector("#your-button-id"));
158
+
159
+ button.setTokenizeCallback(your-callback);
160
+ ```
161
+
162
+ ## Optional configuration
163
+
164
+ ### Accepting multiple currencies
165
+ The payment method also provides an optional preference `merchant_currency_map`.
166
+ This preference allows users to provide different Merchant Account Ids for
167
+ different currencies. If you only plan to accept payment in one currency, the
168
+ defaut Merchant Account Id will be used and you can omit this option.
169
+ An example of setting this preference can be found
170
+ [here](https://github.com/solidusio/solidus_paypal_braintree/blob/master/spec/spec_helper.rb#L70-L72).
171
+
172
+ ### Default store configuration
173
+ The migrations for this gem will add a default configuration to all stores that
174
+ has each payment type disabled. It also adds a `before_create` callback to
175
+ `Spree::Store` that builds a default configuration. You can customize the
176
+ default configuration that gets created by overriding the private
177
+ `build_default_configuration` method on `Spree::Store`.
178
+
179
+ Testing
180
+ -------
181
+
182
+ First bundle your dependencies, then run `rake`. `rake` will default to building the dummy app if it does not exist, then it will run specs, and [Rubocop](https://github.com/bbatsov/rubocop) static code analysis. The dummy app can be regenerated by using `rake test_app`.
183
+
184
+ ```shell
185
+ bundle
186
+ bundle exec rake
187
+ ```
188
+
189
+ When testing your applications integration with this extension you may use it's factories.
190
+ Simply add this require statement to your spec_helper:
191
+
192
+ ```ruby
193
+ require 'solidus_paypal_braintree/factories'
194
+ ```
195
+
196
+ Copyright (c) 2016 Stembolt, released under the New BSD License
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'bundler'
2
+
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ begin
6
+ require 'spree/testing_support/extension_rake'
7
+ require 'rubocop/rake_task'
8
+ require 'rspec/core/rake_task'
9
+
10
+ RSpec::Core::RakeTask.new(:spec)
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %i(first_run rubocop spec)
15
+ rescue LoadError
16
+ # no rspec available
17
+ end
18
+
19
+ task :first_run do
20
+ if Dir['spec/dummy'].empty?
21
+ Rake::Task[:test_app].invoke
22
+ Dir.chdir('../../')
23
+ end
24
+ end
25
+
26
+ desc 'Generates a dummy app for testing'
27
+ task :test_app do
28
+ ENV['LIB_NAME'] = 'solidus_paypal_braintree'
29
+ Rake::Task['extension:test_app'].invoke
30
+ end
@@ -0,0 +1,66 @@
1
+ //= require spree/braintree_hosted_form.js
2
+
3
+ $(function() {
4
+ var $paymentForm = $("#new_payment"),
5
+ $hostedFields = $("[data-braintree-hosted-fields]"),
6
+ hostedFieldsInstance = null;
7
+
8
+ function onError (err) {
9
+ var msg = err.name + ": " + err.message;
10
+ show_flash("error", msg);
11
+ }
12
+
13
+ function showForm(id) {
14
+ $("#card_form" + id).show();
15
+ }
16
+
17
+ function hideForm(id) {
18
+ $("#card_form" + id).hide();
19
+ }
20
+
21
+ function initFields($container, id) {
22
+ function setHostedFieldsInstance(instance) {
23
+ hostedFieldsInstance = instance;
24
+ return instance;
25
+ }
26
+
27
+ if (hostedFieldsInstance === null) {
28
+ braintreeForm = new BraintreeHostedForm($paymentForm, $container, id);
29
+ braintreeForm.initializeHostedFields().
30
+ then(setHostedFieldsInstance).
31
+ then(braintreeForm.addFormHook(onError)).
32
+ fail(onError);
33
+ }
34
+ }
35
+
36
+ // exit early if we're not looking at the New Payment form, or if no
37
+ // SolidusPaypalBraintree payment methods have been configured.
38
+ if (!$paymentForm.length || !$hostedFields.length) { return; }
39
+
40
+ $.when(
41
+ $.getScript("https://js.braintreegateway.com/web/3.9.0/js/client.min.js"),
42
+ $.getScript("https://js.braintreegateway.com/web/3.9.0/js/hosted-fields.min.js")
43
+ ).done(function() {
44
+ $hostedFields.each(function() {
45
+ var $this = $(this),
46
+ $radios = $("[name=card]", $this),
47
+ id = $this.data("payment-method-id");
48
+
49
+ // If we have previous cards, init fields on change of radio button
50
+ if ($radios.length) {
51
+ $radios.on("change", function() {
52
+ if ($(this).val() == 'new') {
53
+ showForm(id);
54
+ initFields($this, id);
55
+ } else {
56
+ hideForm(id);
57
+ }
58
+ });
59
+ } else {
60
+ // If we don't have previous cards, init fields immediately
61
+ initFields($this, id);
62
+ showForm(id);
63
+ }
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,98 @@
1
+ function BraintreeHostedForm($paymentForm, $hostedFields, paymentMethodId) {
2
+ this.paymentForm = $paymentForm;
3
+ this.hostedFields = $hostedFields;
4
+ this.paymentMethodId = paymentMethodId;
5
+ }
6
+
7
+ BraintreeHostedForm.prototype.initializeHostedFields = function() {
8
+ return this.getToken().
9
+ then(this.createClient.bind(this)).
10
+ then(this.createHostedFields());
11
+ };
12
+
13
+ BraintreeHostedForm.prototype.promisify = function (fn, args, self) {
14
+ var d = $.Deferred();
15
+
16
+ fn.apply(self || this, (args || []).concat(function (err, data) {
17
+ if (err) d.reject(err);
18
+ d.resolve(data);
19
+ }));
20
+
21
+ return d.promise();
22
+ };
23
+
24
+ BraintreeHostedForm.prototype.getToken = function () {
25
+ var opts = {
26
+ url: "/solidus_paypal_braintree/client_token",
27
+ method: "POST",
28
+ data: {
29
+ payment_method_id: this.paymentMethodId
30
+ },
31
+ };
32
+
33
+ function onSuccess(data) {
34
+ return data.client_token;
35
+ }
36
+
37
+ return Spree.ajax(opts).then(onSuccess);
38
+ };
39
+
40
+ BraintreeHostedForm.prototype.createClient = function (token) {
41
+ var opts = { authorization: token };
42
+ return this.promisify(braintree.client.create, [opts]);
43
+ };
44
+
45
+ BraintreeHostedForm.prototype.createHostedFields = function () {
46
+ var self = this;
47
+ var id = this.paymentMethodId;
48
+
49
+ return function(client) {
50
+ var opts = {
51
+ client: client,
52
+
53
+ fields: {
54
+ number: {
55
+ selector: "#card_number" + id
56
+ },
57
+
58
+ cvv: {
59
+ selector: "#card_code" + id
60
+ },
61
+
62
+ expirationDate: {
63
+ selector: "#card_expiry" + id
64
+ }
65
+ }
66
+ };
67
+
68
+ return self.promisify(braintree.hostedFields.create, [opts]);
69
+ };
70
+ };
71
+
72
+ BraintreeHostedForm.prototype.addFormHook = function (errorCallback) {
73
+ var self = this;
74
+ var shouldSubmit = false;
75
+
76
+ function submit(payload) {
77
+ shouldSubmit = true;
78
+
79
+ $("#payment_method_nonce", self.hostedFields).val(payload.nonce);
80
+ self.paymentForm.submit();
81
+ }
82
+
83
+ return function(hostedFields) {
84
+ self.paymentForm.on("submit", function(e) {
85
+ if (self.hostedFields.is(":visible") && !shouldSubmit) {
86
+ e.preventDefault();
87
+
88
+ hostedFields.tokenize(function(err, payload) {
89
+ if (err) {
90
+ errorCallback(err);
91
+ } else {
92
+ submit(payload);
93
+ }
94
+ });
95
+ }
96
+ });
97
+ };
98
+ };
@@ -0,0 +1,60 @@
1
+ //= require spree/braintree_hosted_form
2
+
3
+ $(function() {
4
+ /* This provides a default error handler for Braintree. Since we prevent
5
+ * submission if tokenization fails, we need to manually re-enable the
6
+ * submit button. */
7
+ function braintreeError (err) {
8
+ SolidusPaypalBraintree.braintreeErrorHandle(err);
9
+ enableSubmit();
10
+ }
11
+
12
+ function enableSubmit() {
13
+ /* If we're using jquery-ujs on the frontend, it will automatically disable
14
+ * the submit button, but do so in a setTimeout here:
15
+ * https://github.com/rails/jquery-rails/blob/master/vendor/assets/javascripts/jquery_ujs.js#L517
16
+ * The only way we can re-enable it is by delaying longer than that timeout
17
+ * or stopping propagation so their submit handler doesn't run. */
18
+ if ($.rails) {
19
+ setTimeout(function () {
20
+ $.rails.enableFormElement($submitButton);
21
+ $submitButton.attr("disabled", false).removeClass("disabled").addClass("primary");
22
+ }, 100);
23
+ } else {
24
+ $submitButton.attr("disabled", false).removeClass("disabled").addClass("primary");
25
+ }
26
+ }
27
+
28
+ function disableSubmit() {
29
+ $submitButton.attr("disabled", true).removeClass("primary").addClass("disabled");
30
+ }
31
+
32
+ var $paymentForm = $("#checkout_form_payment");
33
+ var $hostedFields = $("[data-braintree-hosted-fields]");
34
+ var $submitButton = $("input[type='submit']", $paymentForm);
35
+ var $checkoutForm = $("#checkout_form_payment");
36
+
37
+ // If we're not using hosted fields, the form doesn't need to wait.
38
+ if ($hostedFields.length > 0) {
39
+ disableSubmit();
40
+ }
41
+
42
+ $checkoutForm.submit(disableSubmit);
43
+
44
+ $.when(
45
+ $.getScript("https://js.braintreegateway.com/web/3.9.0/js/client.min.js"),
46
+ $.getScript("https://js.braintreegateway.com/web/3.9.0/js/hosted-fields.min.js")
47
+ ).done(function() {
48
+ var fieldPromises = $hostedFields.map(function() {
49
+ var $this = $(this);
50
+ var id = $this.data("id");
51
+
52
+ var braintreeForm = new BraintreeHostedForm($paymentForm, $this, id);
53
+ return braintreeForm.initializeHostedFields().
54
+ then(braintreeForm.addFormHook(braintreeError)).
55
+ fail(braintreeError);
56
+ });
57
+
58
+ $.when.apply($, fieldPromises).done(enableSubmit);
59
+ });
60
+ });