solidus_stripe 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +35 -0
  3. data/.gem_release.yml +8 -0
  4. data/.gitignore +11 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +319 -0
  7. data/.travis.yml +28 -0
  8. data/Gemfile +25 -0
  9. data/LICENSE.md +26 -0
  10. data/README.md +115 -0
  11. data/Rakefile +23 -0
  12. data/app/models/spree/payment_method/stripe_credit_card.rb +136 -0
  13. data/bin/rails +7 -0
  14. data/db/migrate/20181010123508_update_stripe_payment_method_type_to_credit_card.rb +21 -0
  15. data/db/seeds.rb +26 -0
  16. data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +6 -0
  17. data/lib/generators/solidus_stripe/install/install_generator.rb +46 -0
  18. data/lib/solidus_stripe.rb +7 -0
  19. data/lib/solidus_stripe/engine.rb +23 -0
  20. data/lib/solidus_stripe/version.rb +5 -0
  21. data/lib/tasks/solidus_stripe/db/seed.rake +14 -0
  22. data/lib/views/api/spree/api/payments/source_views/_stripe.json.jbuilder +3 -0
  23. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +28 -0
  24. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +1 -0
  25. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +1 -0
  26. data/lib/views/frontend/spree/checkout/existing_payment/_stripe.html.erb +1 -0
  27. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +6 -0
  28. data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +77 -0
  29. data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +133 -0
  30. data/solidus_stripe.gemspec +45 -0
  31. data/spec/features/stripe_checkout_spec.rb +279 -0
  32. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +205 -0
  33. data/spec/spec_helper.rb +20 -0
  34. metadata +264 -0
@@ -0,0 +1,28 @@
1
+ <tr>
2
+ <td><%= t(:message, :scope => [:spree, :log_entry, :stripe]) %></td>
3
+ <td><%= entry.parsed_details.message %></td>
4
+ </tr>
5
+ <tr>
6
+ <td><%= t(:charge_id, :scope => [:spree, :log_entry, :stripe]) %></td>
7
+ <td><%= entry.parsed_details.params['id'] %></td>
8
+ </tr>
9
+ <% if card = entry.parsed_details.params['card'] %>
10
+ <tr>
11
+ <td><%= t(:card_id, :scope => [:spree, :log_entry, :stripe]) %></td>
12
+ <td><%= card['id'] %></td>
13
+ </tr>
14
+
15
+ <tr>
16
+ <td><%= t(:cvc_check, :scope => [:spree, :log_entry, :stripe]) %></td>
17
+ <td><%= card['cvc_check'] %></td>
18
+ </tr>
19
+
20
+ <tr>
21
+ <td><%= t(:address_zip_check, :scope => [:spree, :log_entry, :stripe]) %></td>
22
+ <td><%= card['address_zip_check'] %></td>
23
+ </tr>
24
+ <% end %>
25
+ <tr>
26
+ <td><%= t(:cvv_result, :scope => [:spree, :log_entry, :stripe]) %></td>
27
+ <td><%= entry.parsed_details.cvv_result['message'].to_s %></td>
28
+ </tr>
@@ -0,0 +1 @@
1
+ <%= render "spree/admin/payments/source_forms/gateway", payment_method: payment_method, previous_cards: payment_method.reusable_sources(@order) %>
@@ -0,0 +1 @@
1
+ <%= render "spree/admin/payments/source_views/gateway", payment: payment %>
@@ -0,0 +1 @@
1
+ <%= render partial: "spree/checkout/existing_payment/gateway", locals: { wallet_payment_source: wallet_payment_source, default: default } %>
@@ -0,0 +1,6 @@
1
+ <% if payment_method.v3_elements? %>
2
+ <%= render 'spree/checkout/payment/v3/stripe', payment_method: payment_method %>
3
+ <% else %>
4
+ <%= render "spree/checkout/payment/gateway", payment_method: payment_method %>
5
+ <%= render 'spree/checkout/payment/v2/javascript', payment_method: payment_method %>
6
+ <% end %>
@@ -0,0 +1,77 @@
1
+ <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
2
+ <script type="text/javascript">
3
+ Stripe.setPublishableKey("<%= payment_method.preferred_publishable_key %>");
4
+ </script>
5
+
6
+ <script>
7
+ Spree.stripePaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
8
+ var mapCC, stripeResponseHandler;
9
+
10
+ mapCC = function(ccType) {
11
+ if (ccType === 'MasterCard') {
12
+ return 'mastercard';
13
+ } else if (ccType === 'Visa') {
14
+ return 'visa';
15
+ } else if (ccType === 'American Express') {
16
+ return 'amex';
17
+ } else if (ccType === 'Discover') {
18
+ return 'discover';
19
+ } else if (ccType === 'Diners Club') {
20
+ return 'dinersclub';
21
+ } else if (ccType === 'JCB') {
22
+ return 'jcb';
23
+ }
24
+ };
25
+
26
+ stripeResponseHandler = function(status, response) {
27
+ var paymentMethodId, token;
28
+ if (response.error) {
29
+ $('#stripeError').html(response.error.message);
30
+ return $('#stripeError').show();
31
+ } else {
32
+ Spree.stripePaymentMethod.find('#card_number, #card_expiry, #card_code').prop("disabled", true);
33
+ Spree.stripePaymentMethod.find(".ccType").prop("disabled", false);
34
+ Spree.stripePaymentMethod.find(".ccType").val(mapCC(response.card.brand));
35
+ token = response['id'];
36
+ paymentMethodId = Spree.stripePaymentMethod.prop('id').split("_")[2];
37
+ Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][gateway_payment_profile_id]' value='" + token + "'/>");
38
+ Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][last_digits]' value='" + response.card.last4 + "'/>");
39
+ Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][month]' value='" + response.card.exp_month + "'/>");
40
+ Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][year]' value='" + response.card.exp_year + "'/>");
41
+ return Spree.stripePaymentMethod.parents("form").get(0).submit();
42
+ }
43
+ };
44
+
45
+ $(document).ready(function() {
46
+ Spree.stripePaymentMethod.prepend("<div id='stripeError' class='errorExplanation' style='display:none'></div>");
47
+ return $('#checkout_form_payment [data-hook=buttons]').click(function() {
48
+ var expiration, params;
49
+ $('#stripeError').hide();
50
+ if (Spree.stripePaymentMethod.is(':visible')) {
51
+ expiration = $('.cardExpiry:visible').payment('cardExpiryVal');
52
+ params = $.extend({
53
+ number: $('.cardNumber:visible').val(),
54
+ cvc: $('.cardCode:visible').val(),
55
+ exp_month: expiration.month || 0,
56
+ exp_year: expiration.year || 0
57
+ }, Spree.stripeAdditionalInfo);
58
+ Stripe.card.createToken(params, stripeResponseHandler);
59
+ return false;
60
+ }
61
+ });
62
+ });
63
+ </script>
64
+
65
+ <%- if @order.has_checkout_step?('address') -%>
66
+ <script>
67
+ Spree.stripeAdditionalInfo = {
68
+ name: "<%= @order.bill_address.full_name %>",
69
+ address_line1: "<%= @order.bill_address.address1 %>",
70
+ address_line2: "<%= @order.bill_address.address2 %>",
71
+ address_city: "<%= @order.bill_address.city %>",
72
+ address_state: "<%= @order.bill_address.state_text %>",
73
+ address_zip: "<%= @order.bill_address.zipcode %>",
74
+ address_country: "<%= @order.bill_address.country %>"
75
+ }
76
+ </script>
77
+ <%- end -%>
@@ -0,0 +1,133 @@
1
+ <%= image_tag 'credit_cards/credit_card.gif', id: 'credit-card-image' %>
2
+ <% param_prefix = "payment_source[#{payment_method.id}]" %>
3
+
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>
8
+
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>
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'solidus_stripe/version'
6
+
7
+ # encoding: UTF-8
8
+
9
+ Gem::Specification.new do |s|
10
+ s.platform = Gem::Platform::RUBY
11
+ s.name = "solidus_stripe"
12
+ s.version = SolidusStripe::VERSION
13
+ s.summary = "Stripe Payment Method for Solidus"
14
+ s.description = s.summary
15
+ s.required_ruby_version = ">= 2.2"
16
+
17
+ s.author = "Solidus Team"
18
+ s.email = "contact@solidus.io"
19
+ s.homepage = "https://solidus.io"
20
+ s.license = 'BSD-3'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- spec/*`.split("\n")
24
+ s.require_path = "lib"
25
+ s.requirements << "none"
26
+
27
+ s.add_dependency "solidus_core", [">= 2.3", "< 3"]
28
+ s.add_dependency "solidus_support", ">= 0.3.1"
29
+
30
+ # ActiveMerchant v1.58 through v1.59 introduced a breaking change
31
+ # to the stripe gateway.
32
+ #
33
+ # 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"
45
+ end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe "Stripe checkout", type: :feature do
6
+ let(:zone) { FactoryBot.create(:zone) }
7
+ let(:country) { FactoryBot.create(:country) }
8
+
9
+ before do
10
+ FactoryBot.create(:store)
11
+ zone.members << Spree::ZoneMember.create!(zoneable: country)
12
+ FactoryBot.create(:free_shipping_method)
13
+
14
+ Spree::PaymentMethod::StripeCreditCard.create!(
15
+ name: "Stripe",
16
+ preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN",
17
+ preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg",
18
+ preferred_v3_elements: preferred_v3_elements
19
+ )
20
+
21
+ FactoryBot.create(:product, name: "DL-44")
22
+
23
+ visit spree.root_path
24
+ click_link "DL-44"
25
+ click_button "Add To Cart"
26
+
27
+ expect(page).to have_current_path("/cart")
28
+ click_button "Checkout"
29
+
30
+ expect(page).to have_current_path("/checkout/registration")
31
+ click_link "Create a new account"
32
+ within("#new_spree_user") do
33
+ fill_in "Email", with: "mary@example.com"
34
+ fill_in "Password", with: "superStrongPassword"
35
+ fill_in "Password Confirmation", with: "superStrongPassword"
36
+ end
37
+ click_button "Create"
38
+
39
+ # Address
40
+ expect(page).to have_current_path("/checkout/address")
41
+
42
+ within("#billing") do
43
+ fill_in "First Name", with: "Han"
44
+ fill_in "Last Name", with: "Solo"
45
+ fill_in "Street Address", with: "YT-1300"
46
+ fill_in "City", with: "Mos Eisley"
47
+ select "United States of America", from: "Country"
48
+ select country.states.first.name, from: "order_bill_address_attributes_state_id"
49
+ fill_in "Zip", with: "12010"
50
+ fill_in "Phone", with: "(555) 555-5555"
51
+ end
52
+ click_on "Save and Continue"
53
+
54
+ # Delivery
55
+ expect(page).to have_current_path("/checkout/delivery")
56
+ expect(page).to have_content("UPS Ground")
57
+ end
58
+
59
+ # This will fetch a token from Stripe.com and then pass that to the webserver.
60
+ # The server then processes the payment using that token.
61
+
62
+ context 'when using Stripe V2 API library' do
63
+ let(:preferred_v3_elements) { false }
64
+
65
+ before do
66
+ click_on "Save and Continue"
67
+ expect(page).to have_current_path("/checkout/payment")
68
+ end
69
+
70
+ it "can process a valid payment", js: true do
71
+ fill_in "Card Number", with: "4242 4242 4242 4242"
72
+ fill_in "Card Code", with: "123"
73
+ fill_in "Expiration", with: "01 / #{Time.now.year + 1}"
74
+ click_button "Save and Continue"
75
+ expect(page).to have_current_path("/checkout/confirm")
76
+ click_button "Place Order"
77
+ expect(page).to have_content("Your order has been processed successfully")
78
+ end
79
+
80
+ it "can re-use saved cards", js: true do
81
+ fill_in "Card Number", with: "4242 4242 4242 4242"
82
+ fill_in "Card Code", with: "123"
83
+ fill_in "Expiration", with: "01 / #{Time.now.year + 1}"
84
+ click_button "Save and Continue"
85
+
86
+ expect(page).to have_current_path("/checkout/confirm")
87
+ click_button "Place Order"
88
+ expect(page).to have_content("Your order has been processed successfully")
89
+
90
+ visit spree.root_path
91
+ click_link "DL-44"
92
+ click_button "Add To Cart"
93
+
94
+ expect(page).to have_current_path("/cart")
95
+ click_button "Checkout"
96
+
97
+ # Address
98
+ expect(page).to have_current_path("/checkout/address")
99
+
100
+ within("#billing") do
101
+ fill_in "First Name", with: "Han"
102
+ fill_in "Last Name", with: "Solo"
103
+ fill_in "Street Address", with: "YT-1300"
104
+ fill_in "City", with: "Mos Eisley"
105
+ select "United States of America", from: "Country"
106
+ select country.states.first.name, from: "order_bill_address_attributes_state_id"
107
+ fill_in "Zip", with: "12010"
108
+ fill_in "Phone", with: "(555) 555-5555"
109
+ end
110
+ click_on "Save and Continue"
111
+
112
+ # Delivery
113
+ expect(page).to have_current_path("/checkout/delivery")
114
+ expect(page).to have_content("UPS Ground")
115
+ click_on "Save and Continue"
116
+
117
+ # Payment
118
+ expect(page).to have_current_path("/checkout/payment")
119
+ choose "Use an existing card on file"
120
+ click_button "Save and Continue"
121
+
122
+ # Confirm
123
+ expect(page).to have_current_path("/checkout/confirm")
124
+ click_button "Place Order"
125
+ expect(page).to have_content("Your order has been processed successfully")
126
+ end
127
+
128
+ it "shows an error with a missing credit card number", js: true do
129
+ fill_in "Expiration", with: "01 / #{Time.now.year + 1}"
130
+ click_button "Save and Continue"
131
+ expect(page).to have_content("Could not find payment information")
132
+ end
133
+
134
+ it "shows an error with a missing expiration date", js: true do
135
+ fill_in "Card Number", with: "4242 4242 4242 4242"
136
+ click_button "Save and Continue"
137
+ expect(page).to have_content("Your card's expiration year is invalid.")
138
+ end
139
+
140
+ it "shows an error with an invalid credit card number", js: true do
141
+ fill_in "Card Number", with: "1111 1111 1111 1111"
142
+ fill_in "Expiration", with: "01 / #{Time.now.year + 1}"
143
+ click_button "Save and Continue"
144
+ expect(page).to have_content("Your card number is incorrect.")
145
+ end
146
+
147
+ it "shows an error with invalid security fields", js: true do
148
+ fill_in "Card Number", with: "4242 4242 4242 4242"
149
+ fill_in "Expiration", with: "01 / #{Time.now.year + 1}"
150
+ fill_in "Card Code", with: "12"
151
+ click_button "Save and Continue"
152
+ expect(page).to have_content("Your card's security code is invalid.")
153
+ end
154
+
155
+ it "shows an error with invalid expiry fields", js: true do
156
+ fill_in "Card Number", with: "4242 4242 4242 4242"
157
+ fill_in "Expiration", with: "00 / #{Time.now.year + 1}"
158
+ fill_in "Card Code", with: "123"
159
+ click_button "Save and Continue"
160
+ expect(page).to have_content("Your card's expiration month is invalid.")
161
+ end
162
+ end
163
+
164
+ context 'when using Stripe V3 API libarary with Elements' do
165
+ let(:preferred_v3_elements) { true }
166
+
167
+ before do
168
+ click_on "Save and Continue"
169
+ expect(page).to have_current_path("/checkout/payment")
170
+ end
171
+
172
+ it "can process a valid payment", js: true do
173
+ within_frame find('#card_number iframe') do
174
+ '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
175
+ end
176
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
177
+ within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
178
+ click_button "Save and Continue"
179
+ expect(page).to have_current_path("/checkout/confirm")
180
+ click_button "Place Order"
181
+ expect(page).to have_content("Your order has been processed successfully")
182
+ end
183
+
184
+ it "can re-use saved cards", js: true do
185
+ within_frame find('#card_number iframe') do
186
+ '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
187
+ end
188
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
189
+ within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
190
+ click_button "Save and Continue"
191
+ expect(page).to have_current_path("/checkout/confirm")
192
+ click_button "Place Order"
193
+ expect(page).to have_content("Your order has been processed successfully")
194
+
195
+ visit spree.root_path
196
+ click_link "DL-44"
197
+ click_button "Add To Cart"
198
+
199
+ expect(page).to have_current_path("/cart")
200
+ click_button "Checkout"
201
+
202
+ # Address
203
+ expect(page).to have_current_path("/checkout/address")
204
+
205
+ within("#billing") do
206
+ fill_in "First Name", with: "Han"
207
+ fill_in "Last Name", with: "Solo"
208
+ fill_in "Street Address", with: "YT-1300"
209
+ fill_in "City", with: "Mos Eisley"
210
+ select "United States of America", from: "Country"
211
+ select country.states.first.name, from: "order_bill_address_attributes_state_id"
212
+ fill_in "Zip", with: "12010"
213
+ fill_in "Phone", with: "(555) 555-5555"
214
+ end
215
+ click_on "Save and Continue"
216
+
217
+ # Delivery
218
+ expect(page).to have_current_path("/checkout/delivery")
219
+ expect(page).to have_content("UPS Ground")
220
+ click_on "Save and Continue"
221
+
222
+ # Payment
223
+ expect(page).to have_current_path("/checkout/payment")
224
+ choose "Use an existing card on file"
225
+ click_button "Save and Continue"
226
+
227
+ # Confirm
228
+ expect(page).to have_current_path("/checkout/confirm")
229
+ click_button "Place Order"
230
+ expect(page).to have_content("Your order has been processed successfully")
231
+ end
232
+
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.")
238
+ end
239
+
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) }
243
+ 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
+ end
248
+
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) }
252
+ 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
+ end
258
+
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) }
262
+ 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
+ end
268
+
269
+ it "shows an error with invalid expiry fields", js: true do
270
+ within_frame find('#card_number iframe') do
271
+ '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
272
+ end
273
+ within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
274
+ within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "013" }
275
+ click_button "Save and Continue"
276
+ expect(page).to have_content("Your card's expiration date is incomplete.")
277
+ end
278
+ end
279
+ end