solidus_stripe 1.0.0 → 1.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gem_release.yml +2 -5
  3. data/.github/stale.yml +17 -0
  4. data/.gitignore +11 -4
  5. data/.rspec +2 -1
  6. data/.rubocop.yml +2 -319
  7. data/Gemfile +16 -9
  8. data/LICENSE +26 -0
  9. data/README.md +58 -1
  10. data/Rakefile +3 -20
  11. data/app/assets/javascripts/solidus_stripe/stripe-init.js +1 -0
  12. data/app/assets/javascripts/solidus_stripe/stripe-init/base.js +180 -0
  13. data/app/controllers/solidus_stripe/intents_controller.rb +52 -0
  14. data/app/controllers/solidus_stripe/payment_request_controller.rb +42 -0
  15. data/app/controllers/spree/stripe_controller.rb +13 -0
  16. data/app/models/solidus_stripe/address_from_params_service.rb +57 -0
  17. data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +46 -0
  18. data/app/models/solidus_stripe/shipping_rates_service.rb +46 -0
  19. data/app/models/spree/payment_method/stripe_credit_card.rb +57 -8
  20. data/bin/console +17 -0
  21. data/bin/rails +12 -4
  22. data/bin/setup +8 -0
  23. data/config/routes.rb +11 -0
  24. data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +5 -0
  25. data/lib/generators/solidus_stripe/install/install_generator.rb +3 -13
  26. data/lib/solidus_stripe/engine.rb +14 -2
  27. data/lib/solidus_stripe/version.rb +1 -1
  28. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +2 -0
  29. data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +13 -12
  30. data/lib/views/frontend/spree/checkout/payment/v3/_elements_js.html.erb +28 -0
  31. data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +42 -0
  32. data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +5 -0
  33. data/lib/views/frontend/spree/checkout/payment/v3/_intents_js.html.erb +48 -0
  34. data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +3 -131
  35. data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +92 -0
  36. data/solidus_stripe.gemspec +15 -20
  37. data/spec/features/stripe_checkout_spec.rb +196 -35
  38. data/spec/models/solidus_stripe/address_from_params_service_spec.rb +62 -0
  39. data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +65 -0
  40. data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +54 -0
  41. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +44 -5
  42. data/spec/spec_helper.rb +9 -7
  43. metadata +38 -136
@@ -0,0 +1,5 @@
1
+ <%= render 'spree/checkout/payment/v3/form_elements', payment_method: payment_method %>
2
+
3
+ <%= javascript_include_tag "solidus_stripe/stripe-init.js" %>
4
+
5
+ <%= render "spree/checkout/payment/v3/intents_js" %>
@@ -0,0 +1,48 @@
1
+ <script>
2
+ // Stripe Intents JS code
3
+
4
+ var cardNumber = initElements();
5
+ var paymentRequest = setUpPaymentRequest(SolidusStripe.paymentMethod.config.payment_request);
6
+
7
+ form.bind('submit', function(event) {
8
+ if (element.is(':visible')) {
9
+ event.preventDefault();
10
+
11
+ errorElement.text('').hide();
12
+
13
+ stripe.createPaymentMethod(
14
+ 'card',
15
+ cardNumber
16
+ ).then(function(result) {
17
+ handlePayment(result);
18
+ });
19
+ }
20
+ });
21
+
22
+ function handlePayment(payment) {
23
+ if (payment.error) {
24
+ showError(payment.error.message);
25
+ } else {
26
+ stripeTokenHandler(payment.paymentMethod);
27
+ fetch('/stripe/confirm_intents', {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json'
31
+ },
32
+ body: JSON.stringify({
33
+ spree_payment_method_id: SolidusStripe.paymentMethod.config.id,
34
+ stripe_payment_method_id: payment.paymentMethod.id,
35
+ authenticity_token: authToken
36
+ })
37
+ }).then(function(response) {
38
+ response.json().then(function(json) {
39
+ handleServerResponse(json, payment);
40
+ })
41
+ });
42
+ }
43
+ };
44
+
45
+ function submitPayment(_payment) {
46
+ form.unbind('submit').submit();
47
+ }
48
+ </script>
@@ -1,133 +1,5 @@
1
- <%= image_tag 'credit_cards/credit_card.gif', id: 'credit-card-image' %>
2
- <% param_prefix = "payment_source[#{payment_method.id}]" %>
1
+ <%= render 'spree/checkout/payment/v3/form_elements', payment_method: payment_method %>
3
2
 
4
- <div class="field field-required">
5
- <%= label_tag "name_on_card_#{payment_method.id}", t('spree.name_on_card') %>
6
- <%= text_field_tag "#{param_prefix}[name]", "#{@order.billing_firstname} #{@order.billing_lastname}", { id: "name_on_card_#{payment_method.id}", autocomplete: "cc-name" } %>
7
- </div>
3
+ <%= javascript_include_tag "solidus_stripe/stripe-init.js" %>
8
4
 
9
- <div class="field field-required" data-hook="card_number">
10
- <%= label_tag "card_number", t('spree.card_number') %>
11
- <div id="card_number"></div>
12
- <span id="card_type" style="display:none;">
13
- ( <span id="looks_like" ><%= t('spree.card_type_is') %> <span id="type"></span></span>
14
- <span id="unrecognized"><%= t('spree.unrecognized_card_type') %></span>
15
- )
16
- </span>
17
- </div>
18
-
19
- <div class="field field-required" data-hook="card_expiration">
20
- <%= label_tag "card_expiry", t('spree.expiration') %>
21
- <div id="card_expiry"></div>
22
- </div>
23
-
24
- <div class="field field-required" data-hook="card_code">
25
- <%= label_tag "card_cvc", t('spree.card_code') %>
26
- <div id="card_cvc"></div>
27
- <%= link_to "(#{t('spree.what_is_this')})", spree.cvv_path, target: '_blank', "data-hook" => "cvv_link", id: "cvv_link" %>
28
- </div>
29
-
30
- <div id="card-errors" class='errorExplanation' role="alert" style="display: none"></div>
31
-
32
- <% if @order.bill_address %>
33
- <%= fields_for "#{param_prefix}[address_attributes]", @order.bill_address do |f| %>
34
- <%= render partial: 'spree/address/form_hidden', locals: { form: f } %>
35
- <% end %>
36
- <% end %>
37
-
38
- <%= hidden_field_tag "#{param_prefix}[cc_type]", '', id: "cc_type", class: 'ccType' %>
39
-
40
- <script src="https://js.stripe.com/v3/"></script>
41
-
42
- <script>
43
- Spree.stripePaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
44
-
45
- var stripe = Stripe("<%= payment_method.preferred_publishable_key %>");
46
-
47
- var elements = stripe.elements({locale: 'en'});
48
-
49
- var style = {
50
- base: {
51
- color: 'black',
52
- fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
53
- fontSmoothing: 'antialiased',
54
- fontSize: '14px',
55
- '::placeholder': {
56
- color: 'silver'
57
- }
58
- },
59
- invalid: {
60
- color: 'red',
61
- iconColor: 'red'
62
- }
63
- };
64
-
65
- var cardNumber = elements.create('cardNumber', {style: style});
66
- var cardExpiry = elements.create('cardExpiry', {style: style});
67
- var cardCvc = elements.create('cardCvc', {style: style});
68
-
69
- cardNumber.mount('#card_number');
70
- cardExpiry.mount('#card_expiry');
71
- cardCvc.mount('#card_cvc');
72
-
73
- $(function() {
74
- var form = Spree.stripePaymentMethod.parents('form');
75
- var submitButton = form.find('input[type="submit"]');
76
- var errorElement = form.find('#card-errors');
77
- var cardType = form.find('input#cc_type');
78
-
79
- cardNumber.addEventListener('change', function(event) {
80
- if (event.error) {
81
- errorElement.text(event.error.message).show();
82
- } else {
83
- errorElement.hide().text('');
84
- }
85
- });
86
-
87
- form.bind('submit', function(event) {
88
- if (Spree.stripePaymentMethod.is(':visible')) {
89
- event.preventDefault();
90
-
91
- stripe.createToken(cardNumber).then(function(result) {
92
- if (result.error) {
93
- errorElement.text(result.error.message).show();
94
- setTimeout(function() {
95
- $.rails.enableElement(submitButton[0]);
96
- submitButton.removeAttr('disabled').removeClass('disabled');
97
- }, 100);
98
- } else {
99
- stripeTokenHandler(result.token);
100
- }
101
- });
102
- }
103
- });
104
-
105
- function stripeTokenHandler(token) {
106
- var paymentMethodId = Spree.stripePaymentMethod.prop('id').split("_")[2];
107
- var baseSelector = `<input type='hidden' class='stripeToken' name='payment_source[${paymentMethodId}]`;
108
-
109
- Spree.stripePaymentMethod.append(`${baseSelector}[gateway_payment_profile_id]' value='${token.id}'/>`);
110
- Spree.stripePaymentMethod.append(`${baseSelector}[last_digits]' value='${token.card.last4}'/>`);
111
- Spree.stripePaymentMethod.append(`${baseSelector}[month]' value='${token.card.exp_month}'/>`);
112
- Spree.stripePaymentMethod.append(`${baseSelector}[year]' value='${token.card.exp_year}'/>`);
113
- cardType.val(mapCC(token.card.type));
114
- form[0].submit();
115
- };
116
-
117
- function mapCC(ccType) {
118
- if (ccType === 'MasterCard') {
119
- return 'mastercard';
120
- } else if (ccType === 'Visa') {
121
- return 'visa';
122
- } else if (ccType === 'American Express') {
123
- return 'amex';
124
- } else if (ccType === 'Discover') {
125
- return 'discover';
126
- } else if (ccType === 'Diners Club') {
127
- return 'dinersclub';
128
- } else if (ccType === 'JCB') {
129
- return 'jcb';
130
- }
131
- };
132
- })
133
- </script>
5
+ <%= render "spree/checkout/payment/v3/elements_js" %>
@@ -0,0 +1,92 @@
1
+ <% if current_order.present? && cart_checkout_payment_method.present? %>
2
+ <div id="stripe-payment-request" class="stripe-payment-request" style="display:none">
3
+ <div id="payment-request-button"
4
+ data-stripe-config="<%= cart_checkout_payment_method.stripe_config(current_order).to_json %>"
5
+ data-order-token="<%= current_order.guest_token %>"
6
+ data-submit-url="<%= api_checkout_path(current_order.number) %>"
7
+ data-complete-url="<%= checkout_path %>"
8
+ class="payment-request-button">
9
+ </div>
10
+
11
+ <div id="card-errors" class='errorExplanation' role="alert" style="display: none">
12
+ </div>
13
+ </div>
14
+
15
+ <script src="https://js.stripe.com/v3/"></script>
16
+ <%= javascript_include_tag "solidus_stripe/stripe-init" %>
17
+
18
+ <script>
19
+ var paymentRequestConfig = SolidusStripe.paymentMethod.config.payment_request;
20
+ var errorElement = $('#card-errors');
21
+
22
+ if (typeof paymentRequestConfig !== 'undefined') {
23
+ paymentRequestConfig.requestShipping = true;
24
+
25
+ var onPrButtonMounted = function(buttonId, success) {
26
+ var container = document.getElementById('stripe-payment-request');
27
+
28
+ if (success) {
29
+ container.style.display = '';
30
+ } else {
31
+ container.style.display = 'none';
32
+ }
33
+ };
34
+
35
+ var paymentRequest = setUpPaymentRequest(paymentRequestConfig, onPrButtonMounted);
36
+
37
+ function handlePayment(result) {
38
+ fetch('/stripe/update_order', {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify({
42
+ shipping_address: result.shippingAddress,
43
+ shipping_option: result.shippingOption,
44
+ email: result.payerEmail,
45
+ name: result.payerName,
46
+ authenticity_token: authToken
47
+ })
48
+ }).then(function(response) {
49
+ response.json().then(function(json) {
50
+ handleServerResponse(json, result);
51
+ })
52
+ });
53
+ };
54
+
55
+ function stripeTokenHandler(token) {
56
+ return {
57
+ order: {
58
+ payments_attributes: [
59
+ {
60
+ payment_method_id: SolidusStripe.paymentMethod.config.id,
61
+ source_attributes: {
62
+ gateway_payment_profile_id: token.id,
63
+ last_digits: token.card.last4,
64
+ month: token.card.exp_month,
65
+ year: token.card.exp_year
66
+ }
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ };
72
+
73
+ function submitPayment(payment) {
74
+ $.ajax({
75
+ url : $('[data-submit-url]').data('submit-url'),
76
+ headers: {
77
+ 'X-Spree-Order-Token': $('[data-order-token]').data('order-token')
78
+ },
79
+ type : 'PATCH',
80
+ contentType: 'application/json',
81
+ data : JSON.stringify(stripeTokenHandler(payment.paymentMethod)),
82
+ success: function() {
83
+ window.location = $('[data-complete-url]').data('complete-url');
84
+ },
85
+ error: function(xhr,status,error) {
86
+ showError(xhr.responseJSON.error);
87
+ }
88
+ });
89
+ };
90
+ }
91
+ </script>
92
+ <% end %>
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ $:.push File.expand_path('lib', __dir__)
5
4
  require 'solidus_stripe/version'
6
5
 
7
- # encoding: UTF-8
8
-
9
6
  Gem::Specification.new do |s|
10
- s.platform = Gem::Platform::RUBY
11
- s.name = "solidus_stripe"
12
- s.version = SolidusStripe::VERSION
7
+ s.name = 'solidus_stripe'
8
+ s.version = SolidusStripe::VERSION
13
9
  s.summary = "Stripe Payment Method for Solidus"
14
10
  s.description = s.summary
15
11
  s.required_ruby_version = ">= 2.2"
@@ -19,27 +15,26 @@ Gem::Specification.new do |s|
19
15
  s.homepage = "https://solidus.io"
20
16
  s.license = 'BSD-3'
21
17
 
18
+ if s.respond_to?(:metadata)
19
+ s.metadata["homepage_uri"] = s.homepage if s.homepage
20
+ s.metadata["source_code_uri"] = s.homepage if s.homepage
21
+ end
22
+
22
23
  s.files = `git ls-files`.split("\n")
23
24
  s.test_files = `git ls-files -- spec/*`.split("\n")
24
25
  s.require_path = "lib"
25
26
  s.requirements << "none"
26
27
 
27
- s.add_dependency "solidus_core", [">= 2.3", "< 3"]
28
- s.add_dependency "solidus_support", ">= 0.3.1"
28
+ s.bindir = "exe"
29
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
30
 
31
+ s.add_dependency 'solidus_core', ['>= 2.3', '< 3']
32
+ s.add_dependency 'solidus_support', '~> 0.4.0'
30
33
  # ActiveMerchant v1.58 through v1.59 introduced a breaking change
31
34
  # to the stripe gateway.
32
35
  #
33
36
  # This was resolved in v1.60, but we still need to skip 1.58 & 1.59.
34
- s.add_dependency "activemerchant", "~> 1.48", "!= 1.58.0", "!= 1.59.0"
35
-
36
- s.add_development_dependency "capybara"
37
- s.add_development_dependency "capybara-screenshot"
38
- s.add_development_dependency "database_cleaner", "~> 1.5"
39
- s.add_development_dependency "factory_bot", "~> 4.4"
40
- s.add_development_dependency "gem-release", "~> 2.0"
41
- s.add_development_dependency "rspec-rails", "~> 3.2"
42
- s.add_development_dependency 'selenium-webdriver', '~> 3.142'
43
- s.add_development_dependency "simplecov"
44
- s.add_development_dependency "sqlite3"
37
+ s.add_dependency "activemerchant", ">= 1.100" # includes "Stripe Payment Intents: Fix fallback for Store"
38
+
39
+ s.add_development_dependency 'solidus_dev_support'
45
40
  end
@@ -15,7 +15,8 @@ RSpec.describe "Stripe checkout", type: :feature do
15
15
  name: "Stripe",
16
16
  preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN",
17
17
  preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg",
18
- preferred_v3_elements: preferred_v3_elements
18
+ preferred_v3_elements: preferred_v3_elements,
19
+ preferred_v3_intents: preferred_v3_intents
19
20
  )
20
21
 
21
22
  FactoryBot.create(:product, name: "DL-44")
@@ -61,6 +62,7 @@ RSpec.describe "Stripe checkout", type: :feature do
61
62
 
62
63
  context 'when using Stripe V2 API library' do
63
64
  let(:preferred_v3_elements) { false }
65
+ let(:preferred_v3_intents) { false }
64
66
 
65
67
  before do
66
68
  click_on "Save and Continue"
@@ -161,32 +163,91 @@ RSpec.describe "Stripe checkout", type: :feature do
161
163
  end
162
164
  end
163
165
 
164
- context 'when using Stripe V3 API libarary with Elements' do
166
+ shared_examples "Stripe Elements invalid payments" do
167
+ it "shows an error with a missing credit card number" do
168
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
169
+ within_frame(find '#card_expiry iframe') do
170
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
171
+ end
172
+ click_button "Save and Continue"
173
+ expect(page).to have_content("Your card number is incomplete.")
174
+ end
175
+
176
+ it "shows an error with a missing expiration date" do
177
+ within_frame find('#card_number iframe') do
178
+ '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
179
+ end
180
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
181
+ click_button "Save and Continue"
182
+ expect(page).to have_content("Your card's expiration date is incomplete.")
183
+ end
184
+
185
+ it "shows an error with an invalid credit card number" do
186
+ within_frame find('#card_number iframe') do
187
+ '1111 1111 1111 1111'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
188
+ end
189
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
190
+ within_frame(find '#card_expiry iframe') do
191
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
192
+ end
193
+ click_button "Save and Continue"
194
+ expect(page).to have_content("Your card number is invalid.")
195
+ end
196
+
197
+ it "shows an error with invalid security fields" do
198
+ within_frame find('#card_number iframe') do
199
+ '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
200
+ end
201
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '12' }
202
+ within_frame(find '#card_expiry iframe') do
203
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
204
+ end
205
+ click_button "Save and Continue"
206
+ expect(page).to have_content("Your card's security code is incomplete.")
207
+ end
208
+
209
+ it "shows an error with invalid expiry fields" do
210
+ within_frame find('#card_number iframe') do
211
+ '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
212
+ end
213
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
214
+ within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "013" }
215
+ click_button "Save and Continue"
216
+ expect(page).to have_content("Your card's expiration date is incomplete.")
217
+ end
218
+ end
219
+
220
+ context 'when using Stripe V3 API libarary with Elements', :js do
165
221
  let(:preferred_v3_elements) { true }
222
+ let(:preferred_v3_intents) { false }
166
223
 
167
224
  before do
168
225
  click_on "Save and Continue"
169
226
  expect(page).to have_current_path("/checkout/payment")
170
227
  end
171
228
 
172
- it "can process a valid payment", js: true do
229
+ it "can process a valid payment" do
173
230
  within_frame find('#card_number iframe') do
174
231
  '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
175
232
  end
176
233
  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
177
- within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
234
+ within_frame(find '#card_expiry iframe') do
235
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
236
+ end
178
237
  click_button "Save and Continue"
179
238
  expect(page).to have_current_path("/checkout/confirm")
180
239
  click_button "Place Order"
181
240
  expect(page).to have_content("Your order has been processed successfully")
182
241
  end
183
242
 
184
- it "can re-use saved cards", js: true do
243
+ it "can re-use saved cards" do
185
244
  within_frame find('#card_number iframe') do
186
245
  '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
187
246
  end
188
247
  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
189
- within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
248
+ within_frame(find '#card_expiry iframe') do
249
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
250
+ end
190
251
  click_button "Save and Continue"
191
252
  expect(page).to have_current_path("/checkout/confirm")
192
253
  click_button "Place Order"
@@ -230,50 +291,150 @@ RSpec.describe "Stripe checkout", type: :feature do
230
291
  expect(page).to have_content("Your order has been processed successfully")
231
292
  end
232
293
 
233
- it "shows an error with a missing credit card number", js: true do
234
- within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
235
- within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
236
- click_button "Save and Continue"
237
- expect(page).to have_content("Your card number is incomplete.")
294
+ it_behaves_like "Stripe Elements invalid payments"
295
+ end
296
+
297
+ context "when using Stripe V3 API libarary with Intents", :js do
298
+ let(:preferred_v3_elements) { false }
299
+ let(:preferred_v3_intents) { true }
300
+
301
+ before do
302
+ click_on "Save and Continue"
303
+ expect(page).to have_current_path("/checkout/payment")
238
304
  end
239
305
 
240
- it "shows an error with a missing expiration date", js: true do
241
- within_frame find('#card_number iframe') do
242
- '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
306
+ context "when using a valid 3D Secure card" do
307
+ let(:card_number) { "4000 0027 6000 3184" }
308
+
309
+ it "successfully completes the checkout" do
310
+ within_frame find('#card_number iframe') do
311
+ card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
312
+ end
313
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
314
+ within_frame(find '#card_expiry iframe') do
315
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
316
+ end
317
+
318
+ click_button "Save and Continue"
319
+
320
+ within_3d_secure_modal do
321
+ expect(page).to have_content '$19.99 using 3D Secure'
322
+
323
+ click_button 'Complete authentication'
324
+ end
325
+
326
+ expect(page).to have_current_path("/checkout/confirm")
327
+
328
+ click_button "Place Order"
329
+
330
+ expect(page).to have_content("Your order has been processed successfully")
243
331
  end
244
- within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
245
- click_button "Save and Continue"
246
- expect(page).to have_content("Your card's expiration date is incomplete.")
247
332
  end
248
333
 
249
- it "shows an error with an invalid credit card number", js: true do
250
- within_frame find('#card_number iframe') do
251
- '1111 1111 1111 1111'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
334
+ context "when using a card without enough money" do
335
+ let(:card_number) { "4000 0000 0000 9995" }
336
+
337
+ it "fails the payment" do
338
+ within_frame find('#card_number iframe') do
339
+ card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
340
+ end
341
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
342
+ within_frame(find '#card_expiry iframe') do
343
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
344
+ end
345
+
346
+ click_button "Save and Continue"
347
+
348
+ expect(page).to have_content "Your card has insufficient funds."
252
349
  end
253
- within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
254
- within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
255
- click_button "Save and Continue"
256
- expect(page).to have_content("Your card number is invalid.")
257
350
  end
258
351
 
259
- it "shows an error with invalid security fields", js: true do
260
- within_frame find('#card_number iframe') do
261
- '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
352
+ context "when entering the wrong 3D verification code" do
353
+ let(:card_number) { "4000 0084 0000 1629" }
354
+
355
+ it "fails the payment" do
356
+ within_frame find('#card_number iframe') do
357
+ card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
358
+ end
359
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
360
+ within_frame(find '#card_expiry iframe') do
361
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
362
+ end
363
+
364
+ click_button "Save and Continue"
365
+
366
+ within_3d_secure_modal do
367
+ click_button 'Complete authentication'
368
+ end
369
+
370
+ expect(page).to have_content "Your card was declined."
262
371
  end
263
- within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '12' }
264
- within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
265
- click_button "Save and Continue"
266
- expect(page).to have_content("Your card's security code is incomplete.")
267
372
  end
268
373
 
269
- it "shows an error with invalid expiry fields", js: true do
374
+ it "can re-use saved cards" do
270
375
  within_frame find('#card_number iframe') do
271
- '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
376
+ "4000 0027 6000 3184".split('').each { |n| find_field('cardnumber').native.send_keys(n) }
272
377
  end
273
378
  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
274
- within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "013" }
379
+ within_frame(find '#card_expiry iframe') do
380
+ '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
381
+ end
275
382
  click_button "Save and Continue"
276
- expect(page).to have_content("Your card's expiration date is incomplete.")
383
+
384
+ within_3d_secure_modal do
385
+ click_button 'Complete authentication'
386
+ end
387
+
388
+ expect(page).to have_current_path("/checkout/confirm")
389
+ click_button "Place Order"
390
+ expect(page).to have_content("Your order has been processed successfully")
391
+
392
+ visit spree.root_path
393
+ click_link "DL-44"
394
+ click_button "Add To Cart"
395
+
396
+ expect(page).to have_current_path("/cart")
397
+ click_button "Checkout"
398
+
399
+ # Address
400
+ expect(page).to have_current_path("/checkout/address")
401
+
402
+ within("#billing") do
403
+ fill_in "First Name", with: "Han"
404
+ fill_in "Last Name", with: "Solo"
405
+ fill_in "Street Address", with: "YT-1300"
406
+ fill_in "City", with: "Mos Eisley"
407
+ select "United States of America", from: "Country"
408
+ select country.states.first.name, from: "order_bill_address_attributes_state_id"
409
+ fill_in "Zip", with: "12010"
410
+ fill_in "Phone", with: "(555) 555-5555"
411
+ end
412
+ click_on "Save and Continue"
413
+
414
+ # Delivery
415
+ expect(page).to have_current_path("/checkout/delivery")
416
+ expect(page).to have_content("UPS Ground")
417
+ click_on "Save and Continue"
418
+
419
+ # Payment
420
+ expect(page).to have_current_path("/checkout/payment")
421
+ choose "Use an existing card on file"
422
+ click_button "Save and Continue"
423
+
424
+ # Confirm
425
+ expect(page).to have_current_path("/checkout/confirm")
426
+ click_button "Place Order"
427
+ expect(page).to have_content("Your order has been processed successfully")
428
+ end
429
+
430
+ it_behaves_like "Stripe Elements invalid payments"
431
+ end
432
+
433
+ def within_3d_secure_modal
434
+ within_frame "__privateStripeFrame10" do
435
+ within_frame "challengeFrame" do
436
+ yield
437
+ end
277
438
  end
278
439
  end
279
440
  end