solidus_stripe 1.0.0 → 1.1.0

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