solidus_paypal_braintree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ });