solidus_paypal_braintree 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +40 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.gitignore +18 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +68 -0
  8. data/CHANGELOG.md +258 -0
  9. data/Gemfile +43 -0
  10. data/LICENSE +2 -2
  11. data/README.md +77 -23
  12. data/Rakefile +4 -25
  13. data/app/assets/javascripts/solidus_paypal_braintree/checkout.js +32 -3
  14. data/app/assets/javascripts/solidus_paypal_braintree/client.js +23 -4
  15. data/app/assets/javascripts/solidus_paypal_braintree/constants.js +19 -0
  16. data/app/assets/javascripts/solidus_paypal_braintree/frontend.js +1 -0
  17. data/app/assets/javascripts/solidus_paypal_braintree/hosted_form.js +15 -5
  18. data/app/assets/javascripts/solidus_paypal_braintree/paypal_button.js +47 -20
  19. data/app/assets/javascripts/solidus_paypal_braintree/paypal_messaging.js +22 -0
  20. data/app/decorators/controllers/solidus_paypal_braintree/admin_payments_controller_decorator.rb +11 -0
  21. data/app/decorators/controllers/solidus_paypal_braintree/checkout_controller_decorator.rb +11 -0
  22. data/app/decorators/controllers/solidus_paypal_braintree/client_tokens_controller.rb +41 -0
  23. data/app/decorators/controllers/solidus_paypal_braintree/orders_controller_decorator.rb +11 -0
  24. data/app/decorators/models/solidus_paypal_braintree/spree/store_decorator.rb +20 -0
  25. data/app/decorators/models/solidus_paypal_braintree/spree/user_decorator.rb +13 -0
  26. data/app/helpers/solidus_paypal_braintree/braintree_admin_helper.rb +23 -0
  27. data/app/helpers/solidus_paypal_braintree/braintree_checkout_helper.rb +46 -0
  28. data/app/models/application_record.rb +2 -0
  29. data/app/models/solidus_paypal_braintree/address.rb +30 -0
  30. data/app/models/solidus_paypal_braintree/configuration.rb +26 -3
  31. data/app/models/solidus_paypal_braintree/customer.rb +7 -3
  32. data/app/models/solidus_paypal_braintree/gateway.rb +52 -20
  33. data/app/models/solidus_paypal_braintree/response.rb +3 -2
  34. data/app/models/solidus_paypal_braintree/source.rb +21 -7
  35. data/app/models/solidus_paypal_braintree/transaction.rb +2 -0
  36. data/app/models/solidus_paypal_braintree/transaction_address.rb +30 -12
  37. data/app/models/solidus_paypal_braintree/transaction_import.rb +13 -9
  38. data/app/views/spree/api/payments/source_views/_paypal_braintree.json.jbuilder +3 -0
  39. data/app/views/spree/checkout/existing_payment/_paypal_braintree.html.erb +10 -0
  40. data/app/views/spree/shared/_apple_pay_button.html.erb +2 -2
  41. data/app/views/spree/shared/_braintree_errors.html.erb +11 -17
  42. data/app/views/spree/shared/_braintree_hosted_fields.html.erb +24 -9
  43. data/app/views/spree/shared/_paypal_braintree_head_scripts.html.erb +9 -6
  44. data/app/views/spree/shared/_paypal_cart_button.html.erb +16 -2
  45. data/app/views/spree/shared/_paypal_messaging.html.erb +13 -0
  46. data/bin/console +17 -0
  47. data/bin/rails +15 -0
  48. data/bin/setup +8 -0
  49. data/config/locales/en.yml +10 -0
  50. data/config/locales/it.yml +51 -8
  51. data/config/routes.rb +2 -0
  52. data/db/migrate/20160906201711_create_solidus_paypal_braintree_customers.rb +3 -1
  53. data/db/migrate/20161125172005_add_braintree_configuration_to_stores.rb +5 -7
  54. data/db/migrate/20170505193712_add_null_constraint_to_sources.rb +3 -1
  55. data/db/migrate/20190705115327_add_paypal_button_preferences_to_braintree_configurations.rb +5 -0
  56. data/db/migrate/20190911141712_add_3d_secure_to_braintree_configuration.rb +5 -0
  57. data/lib/controllers/backend/solidus_paypal_braintree/configurations_controller.rb +20 -5
  58. data/lib/controllers/frontend/solidus_paypal_braintree/checkouts_controller.rb +25 -21
  59. data/lib/controllers/frontend/solidus_paypal_braintree/transactions_controller.rb +55 -51
  60. data/lib/generators/solidus_paypal_braintree/install/install_generator.rb +7 -5
  61. data/lib/solidus_paypal_braintree.rb +4 -0
  62. data/lib/solidus_paypal_braintree/country_mapper.rb +4 -2
  63. data/lib/solidus_paypal_braintree/engine.rb +11 -11
  64. data/lib/solidus_paypal_braintree/factories.rb +8 -4
  65. data/lib/solidus_paypal_braintree/request_protection.rb +3 -0
  66. data/lib/solidus_paypal_braintree/version.rb +3 -1
  67. data/lib/views/backend/solidus_paypal_braintree/configurations/list.html.erb +30 -5
  68. data/lib/views/backend/spree/admin/payments/source_forms/_paypal_braintree.html.erb +2 -2
  69. data/lib/views/backend/spree/admin/payments/source_views/_paypal_braintree.html.erb +2 -2
  70. data/lib/views/backend_v1.2/spree/admin/payments/source_forms/_paypal_braintree.html.erb +2 -2
  71. data/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb +4 -2
  72. data/lib/views/frontend/spree/shared/_paypal_checkout_button.html.erb +30 -0
  73. data/solidus_paypal_braintree.gemspec +42 -0
  74. data/spec/controllers/solidus_paypal_braintree/checkouts_controller_spec.rb +99 -0
  75. data/spec/controllers/solidus_paypal_braintree/client_tokens_controller_spec.rb +55 -0
  76. data/spec/controllers/solidus_paypal_braintree/configurations_controller_spec.rb +73 -0
  77. data/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb +183 -0
  78. data/spec/features/backend/configuration_spec.rb +23 -0
  79. data/spec/features/backend/new_payment_spec.rb +137 -0
  80. data/spec/features/frontend/braintree_credit_card_checkout_spec.rb +187 -0
  81. data/spec/features/frontend/paypal_checkout_spec.rb +166 -0
  82. data/spec/fixtures/cassettes/admin/invalid_credit_card.yml +63 -0
  83. data/spec/fixtures/cassettes/admin/resubmit_credit_card.yml +352 -0
  84. data/spec/fixtures/cassettes/admin/valid_credit_card.yml +412 -0
  85. data/spec/fixtures/cassettes/braintree/create_profile.yml +71 -0
  86. data/spec/fixtures/cassettes/braintree/generate_token.yml +63 -0
  87. data/spec/fixtures/cassettes/braintree/token.yml +63 -0
  88. data/spec/fixtures/cassettes/checkout/invalid_credit_card.yml +63 -0
  89. data/spec/fixtures/cassettes/checkout/resubmit_credit_card.yml +216 -0
  90. data/spec/fixtures/cassettes/checkout/update.yml +71 -0
  91. data/spec/fixtures/cassettes/checkout/valid_credit_card.yml +156 -0
  92. data/spec/fixtures/cassettes/gateway/authorize.yml +86 -0
  93. data/spec/fixtures/cassettes/gateway/authorize/credit_card/address.yml +86 -0
  94. data/spec/fixtures/cassettes/gateway/authorize/merchant_account/EUR.yml +154 -0
  95. data/spec/fixtures/cassettes/gateway/authorize/paypal/EUR.yml +90 -0
  96. data/spec/fixtures/cassettes/gateway/authorize/paypal/address.yml +90 -0
  97. data/spec/fixtures/cassettes/gateway/authorized_transaction.yml +73 -0
  98. data/spec/fixtures/cassettes/gateway/cancel/missing.yml +63 -0
  99. data/spec/fixtures/cassettes/gateway/cancel/refunds.yml +272 -0
  100. data/spec/fixtures/cassettes/gateway/cancel/void.yml +201 -0
  101. data/spec/fixtures/cassettes/gateway/capture.yml +141 -0
  102. data/spec/fixtures/cassettes/gateway/complete.yml +157 -0
  103. data/spec/fixtures/cassettes/gateway/credit.yml +208 -0
  104. data/spec/fixtures/cassettes/gateway/purchase.yml +87 -0
  105. data/spec/fixtures/cassettes/gateway/settled_transaction.yml +140 -0
  106. data/spec/fixtures/cassettes/gateway/void.yml +137 -0
  107. data/spec/fixtures/cassettes/source/card_type.yml +267 -0
  108. data/spec/fixtures/cassettes/source/last4.yml +267 -0
  109. data/spec/fixtures/cassettes/transaction/import/valid.yml +71 -0
  110. data/spec/fixtures/cassettes/transaction/import/valid/capture.yml +224 -0
  111. data/spec/fixtures/views/spree/orders/edit.html.erb +50 -0
  112. data/spec/helpers/solidus_paypal_braintree/braintree_admin_helper_spec.rb +17 -0
  113. data/spec/models/solidus_paypal_braintree/address_spec.rb +51 -0
  114. data/spec/models/solidus_paypal_braintree/avs_result_spec.rb +317 -0
  115. data/spec/models/solidus_paypal_braintree/gateway_spec.rb +692 -0
  116. data/spec/models/solidus_paypal_braintree/response_spec.rb +280 -0
  117. data/spec/models/solidus_paypal_braintree/source_spec.rb +360 -0
  118. data/spec/models/solidus_paypal_braintree/transaction_address_spec.rb +253 -0
  119. data/spec/models/solidus_paypal_braintree/transaction_import_spec.rb +283 -0
  120. data/spec/models/solidus_paypal_braintree/transaction_spec.rb +85 -0
  121. data/spec/models/spree/store_spec.rb +14 -0
  122. data/spec/requests/spree/api/orders_controller_spec.rb +36 -0
  123. data/spec/spec_helper.rb +26 -0
  124. data/spec/support/capybara.rb +7 -0
  125. data/spec/support/factories.rb +2 -0
  126. data/spec/support/gateway_helpers.rb +29 -0
  127. data/spec/support/order_ready_for_payment.rb +37 -0
  128. data/spec/support/vcr.rb +42 -0
  129. data/spec/support/views.rb +1 -0
  130. metadata +182 -194
  131. data/app/controllers/solidus_paypal_braintree/client_tokens_controller.rb +0 -22
  132. data/app/helpers/braintree_admin_helper.rb +0 -18
  133. data/app/models/spree/store_decorator.rb +0 -11
  134. data/app/views/spree/shared/_paypal_checkout_button.html.erb +0 -27
  135. data/config/initializers/braintree.rb +0 -1
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016 Stembolt
1
+ Copyright (c) 2016-2020 Stembolt and other contributors
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without modification,
@@ -9,7 +9,7 @@ are permitted provided that the following conditions are met:
9
9
  * Redistributions in binary form must reproduce the above copyright notice,
10
10
  this list of conditions and the following disclaimer in the documentation
11
11
  and/or other materials provided with the distribution.
12
- * Neither the name Spree nor the names of its contributors may be used to
12
+ * Neither the name Solidus nor the names of its contributors may be used to
13
13
  endorse or promote products derived from this software without specific
14
14
  prior written permission.
15
15
 
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  SolidusPaypalBraintree
2
2
  ======================
3
3
 
4
- [![Build Status](https://travis-ci.org/solidusio/solidus_paypal_braintree.svg?branch=master)](https://travis-ci.org/solidusio/solidus_paypal_braintree)
4
+ [![CircleCI](https://circleci.com/gh/solidusio/solidus_paypal_braintree.svg?style=svg)](https://circleci.com/gh/solidusio/solidus_paypal_braintree)
5
5
 
6
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
7
 
@@ -39,14 +39,15 @@ Payment methods can accept preferences either directly entered in admin, or from
39
39
  1. Set static preferences in an initializer
40
40
  ```ruby
41
41
  # config/initializers/spree.rb
42
- Spree::Config.configure do |config|
42
+ Spree::Config.config do |config|
43
43
  config.static_model_preferences.add(
44
44
  SolidusPaypalBraintree::Gateway,
45
45
  'braintree_credentials', {
46
46
  environment: Rails.env.production? ? 'production' : 'sandbox',
47
47
  merchant_id: ENV['BRAINTREE_MERCHANT_ID'],
48
48
  public_key: ENV['BRAINTREE_PUBLIC_KEY'],
49
- private_key: ENV['BRAINTREE_PRIVATE_KEY']
49
+ private_key: ENV['BRAINTREE_PRIVATE_KEY'],
50
+ paypal_flow: 'vault', # 'checkout' is accepted too
50
51
  }
51
52
  )
52
53
  end
@@ -72,7 +73,7 @@ SolidusPaypalBraintree::Gateway.new(
72
73
  ```
73
74
 
74
75
  ### Configure payment types
75
- 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.
76
+ 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. Before proceeding to checkout, ensure you've created a Braintree configuration for your store:
76
77
 
77
78
  1. Visit /solidus_paypal_braintree/configurations/list
78
79
 
@@ -122,8 +123,8 @@ The following is a relatively bare-bones implementation to enable Apple Pay on t
122
123
  amount: "<%= current_order.total %>",
123
124
  shippingContact: {
124
125
  emailAddress: '<%= current_order.email %>',
125
- familyName: '<%= address.firstname %>',
126
- givenName: '<%= address.lastname %>',
126
+ givenName: '<%= address.firstname %>',
127
+ familyName: '<%= address.lastname %>',
127
128
  phoneNumber: '<%= address.phone %>',
128
129
  addressLines: ['<%= address.address1 %>','<%= address.address2 %>'],
129
130
  locality: '<%= address.city %>',
@@ -152,32 +153,32 @@ It will only be displayed if the `SolidusPaypalBraintree::Gateway` payment
152
153
  method is configured to display on the frontend and PayPal is enabled in the
153
154
  store's configuration.
154
155
 
156
+ You can find button configuration options in
157
+ `/solidus_paypal_braintree/configurations/list` if you want to change the color,
158
+ shape, layout, and a few other options. Keep in mind that the `paypal_button_tagline`
159
+ does not work when the `paypal_button_layout` is set to `vertical`, and will be
160
+ ignored in that case.
161
+
155
162
  The checkout view
156
163
  [initializes the PayPal button](/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb)
157
164
  using the
158
- [vault flow](https://developers.braintreepayments.com/guides/paypal/overview/javascript/v3),
159
- which allows the source to be reused.
165
+ [Vault flow](https://developers.braintreepayments.com/guides/paypal/overview/javascript/v3),
166
+ which allows the source to be reused. Please note that PayPal messaging is disabled with vault flow. If you want, you can use [Checkout with PayPal](https://developers.braintreepayments.com/guides/paypal/checkout-with-paypal/javascript/v3)
167
+ instead, which doesn't allow you to reuse sources but allows your customers to pay with their PayPal
168
+ balance and with PayPal financing options ([see setup instructions](#create-a-new-payment-method)).
160
169
 
161
170
  If you are creating your own checkout view or would like to customize the
162
171
  [options that get passed to tokenize](https://braintree.github.io/braintree-web/3.6.3/PayPal.html#tokenize)
163
- , you can initialize your own using the `PaypalButton` JS object:
172
+ , you can initialize your own using the `CreatePaypalButton` JS object:
164
173
 
165
174
  ```javascript
166
- var button = new PaypalButton(document.querySelector("#your-button-id"));
167
-
168
- button.initialize({
175
+ var paypalOptions = {
169
176
  // your configuration options here
170
- });
171
- ```
177
+ }
172
178
 
173
- After successful tokenization, a callback function is invoked that submits the
174
- transaction via AJAX and advances the order to confirm. It is possible to provide
175
- your own callback function to customize the behaviour after tokenize as follows:
179
+ var button = new SolidusPaypalBraintree.createPaypalButton(document.querySelector("#your-button-id"), paypalOptions);
176
180
 
177
- ```javascript
178
- var button = new PaypalButton(document.querySelector("#your-button-id"));
179
-
180
- button.setTokenizeCallback(your-callback);
181
+ button.initialize();
181
182
  ```
182
183
 
183
184
  ### Express checkout from the cart
@@ -187,6 +188,30 @@ A PayPal button can also be included on the cart view to enable express checkout
187
188
  render "spree/shared/paypal_cart_button"
188
189
  ```
189
190
 
191
+ ### PayPal Financing Messaging
192
+
193
+ PayPal offers an [on-site messaging component](https://www.paypal.com/us/webapps/mpp/on-site-messaging) to notify the customer that there are financing options available. This component is included in both the cart and checkout partials, but is disabled by default. To enable this option, you'll need to use the `checkout` flow, and set the `paypal button messaging` option to `true` in your Braintree configuration.
194
+
195
+ You can also include this view partial to implement this messaging component anywhere - for instance, on the product page:
196
+ ```ruby
197
+ render "spree/shared/paypal_messaging, options: {total: @product.price, placement: "product", currency: 'USD'}"
198
+ ```
199
+
200
+ While we provide the messaging component on the payment buttons for cart and checkout, you're expected to move these to where they make the most sense for your frontend. PayPal recommends keeping the messaging directly below wherever the order or product total is located.
201
+
202
+ #### PayPal configuration
203
+
204
+ If your store requires the [phone number into user addresses](https://github.com/solidusio/solidus/blob/859143f3f061de79cc1b385234599422b8ae8e21/core/app/models/spree/address.rb#L151-L153)
205
+ you'll need to configure PayPal to return the phone back when it returns the
206
+ address used by the user:
207
+
208
+ 1. Log into your PayPal account
209
+ 2. Go to Profile -> My Selling Tools -> Website preferences
210
+ 3. Set Contact Telephone to `On (Required Field)` or `On (Optional Field)`
211
+
212
+ Using the option `Off` will not make the address valid and will raise a
213
+ validation error.
214
+
190
215
  ## Optional configuration
191
216
 
192
217
  ### Accepting multiple currencies
@@ -195,7 +220,12 @@ This preference allows users to provide different Merchant Account Ids for
195
220
  different currencies. If you only plan to accept payment in one currency, the
196
221
  defaut Merchant Account Id will be used and you can omit this option.
197
222
  An example of setting this preference can be found
198
- [here](https://github.com/solidusio/solidus_paypal_braintree/blob/master/spec/spec_helper.rb#L70-L72).
223
+ [here](https://github.com/solidusio/solidus_paypal_braintree/blob/bf5fe0e154d38f7c498f1c54450bb4de7608ff04/spec/support/gateway_helpers.rb#L11-L13).
224
+
225
+ In addition to this, you can also specify different PayPal accounts for each
226
+ currency by using the `paypal_payee_email_map` preference. If you only want
227
+ to use one PayPal account for all currencies, then you can ignore this option.
228
+ You can find an example of setting this preference [here](https://github.com/solidusio/solidus_paypal_braintree/blob/bf5fe0e154d38f7c498f1c54450bb4de7608ff04/spec/support/gateway_helpers.rb#L14-L16).
199
229
 
200
230
  ### Default store configuration
201
231
  The migrations for this gem will add a default configuration to all stores that
@@ -204,9 +234,33 @@ has each payment type disabled. It also adds a `before_create` callback to
204
234
  default configuration that gets created by overriding the private
205
235
  `build_default_configuration` method on `Spree::Store`.
206
236
 
237
+ ### Hosted Fields Styling
238
+ You can style the Braintree credit card fields by using the `credit_card_fields_style` preference on the payment method. The `credit_card_fields_style` will be passed to the `style` key when initializing the credit card fields. You can find more information about styling hosted fields can be found [here.](https://developers.braintreepayments.com/guides/hosted-fields/styling/javascript/v3)
239
+
240
+ You can also use the `placeholder_text` preference on the payment method to set the placeholder text you'd like to use for each of the hosted fields. You'll pass the field name in as the key, and the placeholder text you'd like to use as the value. For example:
241
+ ```ruby
242
+ { number: "Enter card number", cvv: "Enter CVV", expirationDate: "mm/yy" }
243
+ ```
244
+
245
+ ### 3D Secure
246
+
247
+ This gem supports [3D Secure 2](https://developers.braintreepayments.com/guides/3d-secure/overview),
248
+ which satisfies the [Strong Customer Authentication (SCA)](https://www.braintreepayments.com/blog/getting-up-to-speed-on-psd2-regulation-2/)
249
+ requirements introduced by PSD2.
250
+
251
+ 3D Secure can be enabled from Solidus Admin -> Braintree (left-side menu) ->
252
+ tick _3D Secure_ checkbox.
253
+
254
+ Once enabled, you can use the following card numbers to test 3DS 2 on your
255
+ client side in sandbox:
256
+ https://developers.braintreepayments.com/guides/3d-secure/migration/javascript/v3#client-side-sandbox-testing.
257
+
207
258
  Testing
208
259
  -------
209
260
 
261
+ To run the specs it is required to set the Braintree test account data in these environment variables:
262
+ `BRAINTREE_PUBLIC_KEY`, `BRAINTREE_PRIVATE_KEY`, `BRAINTREE_MERCHANT_ID` and `BRAINTREE_PAYPAL_PAYEE_EMAIL`
263
+
210
264
  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`.
211
265
 
212
266
  ```shell
@@ -221,4 +275,4 @@ Simply add this require statement to your spec_helper:
221
275
  require 'solidus_paypal_braintree/factories'
222
276
  ```
223
277
 
224
- Copyright (c) 2016 Stembolt, released under the New BSD License
278
+ Copyright (c) 2016-2020 Stembolt and others contributors, released under the New BSD License
data/Rakefile CHANGED
@@ -1,27 +1,6 @@
1
- require 'bundler'
1
+ # frozen_string_literal: true
2
2
 
3
- Bundler::GemHelper.install_tasks
3
+ require 'solidus_dev_support/rake_tasks'
4
+ SolidusDevSupport::RakeTasks.install
4
5
 
5
- begin
6
- require 'spree/testing_support/extension_rake'
7
- require 'rspec/core/rake_task'
8
-
9
- RSpec::Core::RakeTask.new(:spec)
10
-
11
- task default: %i(first_run spec)
12
- rescue LoadError
13
- # no rspec available
14
- end
15
-
16
- task :first_run do
17
- if Dir['spec/dummy'].empty?
18
- Rake::Task[:test_app].invoke
19
- Dir.chdir('../../')
20
- end
21
- end
22
-
23
- desc 'Generates a dummy app for testing'
24
- task :test_app do
25
- ENV['LIB_NAME'] = 'solidus_paypal_braintree'
26
- Rake::Task['extension:test_app'].invoke
27
- end
6
+ task default: 'extension:specs'
@@ -15,11 +15,18 @@ $(function() {
15
15
  * https://github.com/rails/jquery-rails/blob/master/vendor/assets/javascripts/jquery_ujs.js#L517
16
16
  * The only way we can re-enable it is by delaying longer than that timeout
17
17
  * or stopping propagation so their submit handler doesn't run. */
18
- if ($.rails) {
18
+ if ($.rails && typeof $.rails.enableFormElement !== 'undefined') {
19
19
  setTimeout(function () {
20
20
  $.rails.enableFormElement($submitButton);
21
21
  $submitButton.attr("disabled", false).removeClass("disabled").addClass("primary");
22
22
  }, 100);
23
+ } else if (typeof Rails !== 'undefined' && typeof Rails.enableElement !== 'undefined') {
24
+ /* Indicates that we have rails-ujs instead of jquery-ujs. Rails-ujs was added to rails
25
+ * core in Rails 5.1.0 */
26
+ setTimeout(function () {
27
+ Rails.enableElement($submitButton[0]);
28
+ $submitButton.attr("disabled", false).removeClass("disabled").addClass("primary");
29
+ }, 100);
23
30
  } else {
24
31
  $submitButton.attr("disabled", false).removeClass("disabled").addClass("primary");
25
32
  }
@@ -37,16 +44,38 @@ $(function() {
37
44
  var $nonce = $("#payment_method_nonce", $field);
38
45
 
39
46
  if ($nonce.length > 0 && $nonce.val() === "") {
47
+ var client = braintreeForm._merchantConfigurationOptions._solidusClient;
48
+
40
49
  event.preventDefault();
41
50
  disableSubmit();
42
51
 
43
52
  braintreeForm.tokenize(function(error, payload) {
44
53
  if (error) {
45
54
  braintreeError(error);
46
- } else {
47
- $nonce.val(payload.nonce);
55
+ return;
56
+ }
57
+
58
+ $nonce.val(payload.nonce);
59
+
60
+ if (!client.useThreeDSecure) {
48
61
  $paymentForm.submit();
62
+ return;
63
+ }
64
+
65
+ threeDSecureOptions.nonce = payload.nonce;
66
+ threeDSecureOptions.bin = payload.details.bin;
67
+ threeDSecureOptions.onLookupComplete = function(data, next) {
68
+ next();
49
69
  }
70
+ client._threeDSecureInstance.verifyCard(threeDSecureOptions, function(error, response) {
71
+ if (error === null && (!response.liabilityShiftPossible || response.liabilityShifted)) {
72
+ $nonce.val(response.nonce);
73
+ $paymentForm.submit();
74
+ } else {
75
+ $nonce.val('');
76
+ braintreeError(error || { code: 'THREEDS_AUTHENTICATION_FAILED' });
77
+ }
78
+ });
50
79
  });
51
80
  }
52
81
  }
@@ -57,10 +57,12 @@ SolidusPaypalBraintree.Client = function(config) {
57
57
  this.useDataCollector = config.useDataCollector;
58
58
  this.usePaypal = config.usePaypal;
59
59
  this.useApplepay = config.useApplepay;
60
+ this.useThreeDSecure = config.useThreeDSecure;
60
61
 
61
62
  this._braintreeInstance = null;
62
63
  this._dataCollectorInstance = null;
63
64
  this._paypalInstance = null;
65
+ this._threeDSecureInstance = null;
64
66
  };
65
67
 
66
68
  /**
@@ -71,18 +73,22 @@ SolidusPaypalBraintree.Client.prototype.initialize = function() {
71
73
  var initializationPromise = this._fetchToken().
72
74
  then(this._createBraintreeInstance.bind(this));
73
75
 
74
- if(this.useDataCollector) {
76
+ if (this.useDataCollector) {
75
77
  initializationPromise = initializationPromise.then(this._createDataCollector.bind(this));
76
78
  }
77
79
 
78
- if(this.usePaypal) {
80
+ if (this.usePaypal) {
79
81
  initializationPromise = initializationPromise.then(this._createPaypal.bind(this));
80
82
  }
81
83
 
82
- if(this.useApplepay) {
84
+ if (this.useApplepay) {
83
85
  initializationPromise = initializationPromise.then(this._createApplepay.bind(this));
84
86
  }
85
87
 
88
+ if (this.useThreeDSecure) {
89
+ initializationPromise = initializationPromise.then(this._createThreeDSecure.bind(this));
90
+ }
91
+
86
92
  return initializationPromise.then(this._invokeReadyCallback.bind(this));
87
93
  };
88
94
 
@@ -173,7 +179,9 @@ SolidusPaypalBraintree.Client.prototype._createPaypal = function() {
173
179
  }]).then(function (paypalInstance) {
174
180
  this._paypalInstance = paypalInstance;
175
181
  return paypalInstance;
176
- }.bind(this));
182
+ }.bind(this), function(error) {
183
+ console.error(error.name + ':', error.message);
184
+ });
177
185
  };
178
186
 
179
187
  SolidusPaypalBraintree.Client.prototype._createApplepay = function() {
@@ -184,3 +192,14 @@ SolidusPaypalBraintree.Client.prototype._createApplepay = function() {
184
192
  return applePayInstance;
185
193
  }.bind(this));
186
194
  };
195
+
196
+ SolidusPaypalBraintree.Client.prototype._createThreeDSecure = function() {
197
+ return SolidusPaypalBraintree.PromiseShim.convertBraintreePromise(braintree.threeDSecure.create, [{
198
+ client: this._braintreeInstance,
199
+ version: 2
200
+ }]).then(function (threeDSecureInstance) {
201
+ this._threeDSecureInstance = threeDSecureInstance;
202
+ }.bind(this), function(error) {
203
+ console.log(error);
204
+ });
205
+ };
@@ -26,6 +26,10 @@ SolidusPaypalBraintree = {
26
26
  return SolidusPaypalBraintree.PaypalButton;
27
27
  },
28
28
 
29
+ paypalMessaging: function() {
30
+ return SolidusPaypalBraintree.PaypalMessaging;
31
+ },
32
+
29
33
  applepayButton: function() {
30
34
  return SolidusPaypalBraintree.ApplepayButton;
31
35
  }
@@ -51,6 +55,10 @@ SolidusPaypalBraintree = {
51
55
  return SolidusPaypalBraintree._factory(SolidusPaypalBraintree.config.classes.paypalButton(), arguments);
52
56
  },
53
57
 
58
+ createPaypalMessaging: function() {
59
+ return SolidusPaypalBraintree._factory(SolidusPaypalBraintree.config.classes.paypalMessaging(), arguments);
60
+ },
61
+
54
62
  createApplePayButton: function() {
55
63
  return SolidusPaypalBraintree._factory(SolidusPaypalBraintree.config.classes.applepayButton(), arguments);
56
64
  },
@@ -60,3 +68,14 @@ SolidusPaypalBraintree = {
60
68
  return new (Function.prototype.bind.apply(klass, [null].concat(normalizedArgs)));
61
69
  }
62
70
  };
71
+
72
+ BraintreeError = {
73
+ DEFAULT: "Something bad happened!",
74
+
75
+ getErrorFromSlug: function(slug) {
76
+ error = BraintreeError.DEFAULT
77
+ if (slug in BraintreeError)
78
+ error = BraintreeError[slug]
79
+ return error
80
+ }
81
+ }
@@ -9,4 +9,5 @@
9
9
  //= require solidus_paypal_braintree/client
10
10
  //= require solidus_paypal_braintree/hosted_form
11
11
  //= require solidus_paypal_braintree/paypal_button
12
+ //= require solidus_paypal_braintree/paypal_messaging
12
13
  //= require solidus_paypal_braintree/apple_pay_button
@@ -4,7 +4,11 @@ SolidusPaypalBraintree.HostedForm = function(paymentMethodId) {
4
4
  };
5
5
 
6
6
  SolidusPaypalBraintree.HostedForm.prototype.initialize = function() {
7
- this.client = SolidusPaypalBraintree.createClient({paymentMethodId: this.paymentMethodId});
7
+ this.client = SolidusPaypalBraintree.createClient({
8
+ paymentMethodId: this.paymentMethodId,
9
+ useThreeDSecure: (typeof(window.threeDSecureOptions) !== 'undefined'),
10
+ });
11
+
8
12
  return this.client.initialize().
9
13
  then(this._createHostedFields.bind(this));
10
14
  };
@@ -15,21 +19,27 @@ SolidusPaypalBraintree.HostedForm.prototype._createHostedFields = function () {
15
19
  }
16
20
 
17
21
  var opts = {
22
+ _solidusClient: this.client,
18
23
  client: this.client.getBraintreeInstance(),
19
24
 
20
25
  fields: {
21
26
  number: {
22
- selector: "#card_number" + this.paymentMethodId
27
+ selector: "#card_number" + this.paymentMethodId,
28
+ placeholder: placeholder_text["number"]
23
29
  },
24
30
 
25
31
  cvv: {
26
- selector: "#card_code" + this.paymentMethodId
32
+ selector: "#card_code" + this.paymentMethodId,
33
+ placeholder: placeholder_text["cvv"]
27
34
  },
28
35
 
29
36
  expirationDate: {
30
- selector: "#card_expiry" + this.paymentMethodId
37
+ selector: "#card_expiry" + this.paymentMethodId,
38
+ placeholder: placeholder_text["expirationDate"]
31
39
  }
32
- }
40
+ },
41
+
42
+ styles: credit_card_fields_style
33
43
  };
34
44
 
35
45
  return SolidusPaypalBraintree.PromiseShim.convertBraintreePromise(braintree.hostedFields.create, [opts]);
@@ -7,6 +7,12 @@
7
7
  SolidusPaypalBraintree.PaypalButton = function(element, paypalOptions, options) {
8
8
  this._element = element;
9
9
  this._paypalOptions = paypalOptions || {};
10
+
11
+ this.locale = paypalOptions['locale'] || "en_US";
12
+ this.style = paypalOptions['style'] || {};
13
+ delete paypalOptions['locale'];
14
+ delete paypalOptions['style'];
15
+
10
16
  this._options = options || {};
11
17
  this._client = null;
12
18
  this._environment = this._paypalOptions.environment || 'sandbox';
@@ -34,17 +40,27 @@ SolidusPaypalBraintree.PaypalButton.prototype.initialize = function() {
34
40
  SolidusPaypalBraintree.PaypalButton.prototype.initializeCallback = function() {
35
41
  this._paymentMethodId = this._client.paymentMethodId;
36
42
 
37
- paypal.Button.render({
38
- env: this._environment,
43
+ this._client.getPaypalInstance().loadPayPalSDK({
44
+ currency: this._paypalOptions.currency,
45
+ commit: true,
46
+ vault: this._paypalOptions.flow == "vault",
47
+ components: this.style['messaging'] == "true" && this._paypalOptions.flow != "vault" ? "buttons,messages" : "buttons",
48
+ intent: this._paypalOptions.flow == "vault" ? "tokenize" : "authorize"
49
+ }).then(() => {
50
+ var create_method = this._paypalOptions.flow == "vault" ? "createBillingAgreement" : "createOrder"
39
51
 
40
- payment: function () {
41
- return this._client.getPaypalInstance().createPayment(this._paypalOptions);
42
- }.bind(this),
52
+ var render_config = {
53
+ style: this.style,
54
+ [create_method]: function () {
55
+ return this._client.getPaypalInstance().createPayment(this._paypalOptions);
56
+ }.bind(this),
57
+ onApprove: function (data, actions) {
58
+ return this._client.getPaypalInstance().tokenizePayment(data, this._tokenizeCallback.bind(this));
59
+ }.bind(this)
60
+ };
43
61
 
44
- onAuthorize: function (data, actions) {
45
- return this._client.getPaypalInstance().tokenizePayment(data, this._tokenizeCallback.bind(this));
46
- }.bind(this)
47
- }, this._element);
62
+ paypal.Buttons(render_config).render(this._element);
63
+ })
48
64
  };
49
65
 
50
66
  /**
@@ -70,7 +86,22 @@ SolidusPaypalBraintree.PaypalButton.prototype._tokenizeCallback = function(token
70
86
  window.location.href = response.redirectUrl;
71
87
  },
72
88
  error: function(xhr) {
73
- console.error("Error submitting transaction");
89
+ var errorText = BraintreeError.DEFAULT;
90
+
91
+ if (xhr.responseJSON && xhr.responseJSON.errors) {
92
+ var errors = [];
93
+ $.each(xhr.responseJSON.errors, function(key, values) {
94
+ $.each(values, function(index, value) {
95
+ errors.push(key + " " + value)
96
+ });
97
+ });
98
+
99
+ if (errors.length > 0)
100
+ errorText = errors.join(", ");
101
+ }
102
+
103
+ console.error("Error submitting transaction: " + errorText);
104
+ SolidusPaypalBraintree.showError(errorText);
74
105
  },
75
106
  });
76
107
  };
@@ -102,22 +133,18 @@ SolidusPaypalBraintree.PaypalButton.prototype._transactionParams = function(payl
102
133
  * @param {object} payload - The payload returned by Braintree after tokenization
103
134
  */
104
135
  SolidusPaypalBraintree.PaypalButton.prototype._addressParams = function(payload) {
105
- var first_name, last_name;
136
+ var name;
106
137
  var payload_address = payload.details.shippingAddress || payload.details.billingAddress;
138
+ if (!payload_address) return {};
107
139
 
108
140
  if (payload_address.recipientName) {
109
- first_name = payload_address.recipientName.split(" ")[0];
110
- last_name = payload_address.recipientName.split(" ")[1];
111
- }
112
-
113
- if (!first_name || !last_name) {
114
- first_name = payload.details.firstName;
115
- last_name = payload.details.lastName;
141
+ name = payload_address.recipientName
142
+ } else {
143
+ name = payload.details.firstName + " " + payload.details.lastName;
116
144
  }
117
145
 
118
146
  return {
119
- "first_name" : first_name,
120
- "last_name" : last_name,
147
+ "name" : name,
121
148
  "address_line_1" : payload_address.line1,
122
149
  "address_line_2" : payload_address.line2,
123
150
  "city" : payload_address.city,