solidus_stripe 1.0.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 (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