spree_gateway 3.7.1 → 3.8.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +31 -38
  3. data/Appraisals +4 -10
  4. data/README.md +9 -20
  5. data/app/models/spree/check.rb +41 -0
  6. data/app/models/spree/gateway/stripe_ach_gateway.rb +60 -0
  7. data/app/models/spree/payment_decorator.rb +29 -0
  8. data/config/initializers/spree_permitted_attributes.rb +5 -0
  9. data/config/locales/en.yml +19 -0
  10. data/db/migrate/20200317135551_add_spree_check_payment_source.rb +22 -0
  11. data/gemfiles/{spree_3_2.gemfile → spree_4_1.gemfile} +1 -1
  12. data/gemfiles/{spree_3_5.gemfile → spree_4_2.gemfile} +1 -1
  13. data/lib/active_merchant/billing/stripe_gateway_decorator.rb +13 -0
  14. data/lib/controllers/spree/apple_pay_domain_verification_controller.rb +1 -1
  15. data/lib/spree_gateway/engine.rb +1 -0
  16. data/lib/spree_gateway/version.rb +1 -1
  17. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_ach.html.erb +86 -0
  18. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_apple_pay.html.erb +0 -0
  19. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_elements.html.erb +79 -0
  20. data/lib/views/backend/spree/admin/payments/source_views/_stripe_ach.html.erb +21 -0
  21. data/lib/views/backend/spree/admin/payments/source_views/_stripe_apple_pay.html.erb +1 -0
  22. data/lib/views/backend/spree/admin/payments/source_views/_stripe_elements.html.erb +1 -0
  23. data/lib/views/frontend/spree/checkout/payment/_stripe_ach.html.erb +81 -0
  24. data/lib/views/frontend/spree/checkout/payment/_stripe_ach_verify.html.erb +16 -0
  25. data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +10 -1
  26. data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +18 -23
  27. data/spec/factories/check_factory.rb +10 -0
  28. data/spec/features/admin/stripe_elements_payment_spec.rb +109 -0
  29. data/spec/features/stripe_checkout_spec.rb +3 -0
  30. data/spec/models/gateway/stripe_ach_gateway_spec.rb +185 -0
  31. data/spec/spec_helper.rb +7 -64
  32. data/spree_gateway.gemspec +1 -20
  33. metadata +25 -274
  34. data/gemfiles/spree_3_7.gemfile +0 -9
  35. data/gemfiles/spree_4_0.gemfile +0 -8
@@ -0,0 +1,79 @@
1
+ <%= render "spree/admin/payments/source_forms/gateway", payment_method: payment_method, previous_cards: payment_method.reusable_sources(@order) %>
2
+
3
+ <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
4
+ <script type="text/javascript">
5
+ Stripe.setPublishableKey("<%= payment_method.preferred_publishable_key %>");
6
+ </script>
7
+
8
+ <script>
9
+ var mapCC, stripeElementResponseHandler;
10
+
11
+ mapCC = function(ccType) {
12
+ if (ccType === 'MasterCard') {
13
+ return 'mastercard';
14
+ } else if (ccType === 'Visa') {
15
+ return 'visa';
16
+ } else if (ccType === 'American Express') {
17
+ return 'amex';
18
+ } else if (ccType === 'Discover') {
19
+ return 'discover';
20
+ } else if (ccType === 'Diners Club') {
21
+ return 'dinersclub';
22
+ } else if (ccType === 'JCB') {
23
+ return 'jcb';
24
+ }
25
+ };
26
+ stripeElementResponseHandler = function(status, response) {
27
+ var paymentMethodId, token;
28
+ if (response.error) {
29
+ $('#stripeError').html(response.error.message);
30
+ var param_map = {
31
+ number: '#card_number',
32
+ exp_month: '#card_expiry',
33
+ exp_year: '#card_expiry',
34
+ cvc: '#card_code'
35
+ }
36
+ if (response.error.param) {
37
+ errorField = Spree.stripeElementsPaymentMethod.find(param_map[response.error.param])
38
+ errorField.addClass('error');
39
+ errorField.parent().addClass('has-error');
40
+ }
41
+ return $('#stripeError').show();
42
+ } else {
43
+ Spree.stripeElementsPaymentMethod.find('#card_number<%= payment_method.id %>, #card_expiry<%= payment_method.id %>, #card_code<%= payment_method.id %>').prop("disabled", true);
44
+ Spree.stripeElementsPaymentMethod.find(".ccType").prop("disabled", false);
45
+ Spree.stripeElementsPaymentMethod.find(".ccType").val(mapCC(response.card.brand));
46
+ token = response['id'];
47
+ paymentMethodId = Spree.stripeElementsPaymentMethod.prop('id').split("_")[2];
48
+ Spree.stripeElementsPaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][gateway_payment_profile_id]' value='" + token + "'/>");
49
+ Spree.stripeElementsPaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][last_digits]' value='" + response.card.last4 + "'/>");
50
+ Spree.stripeElementsPaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][month]' value='" + response.card.exp_month + "'/>");
51
+ Spree.stripeElementsPaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][year]' value='" + response.card.exp_year + "'/>");
52
+ return Spree.stripeElementsPaymentMethod.parents("form").trigger('submit');
53
+ }
54
+ };
55
+
56
+ window.addEventListener('DOMContentLoaded', function() {
57
+ Spree.stripeElementsPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
58
+
59
+ Spree.ready(function() {
60
+ Spree.stripeElementsPaymentMethod.prepend("<div id='stripeError' class='errorExplanation alert alert-danger' style='display:none'></div>");
61
+ return $('#new_payment [data-hook=buttons]').click(function() {
62
+ var expiration, params;
63
+ $('#stripeError').hide();
64
+ Spree.stripeElementsPaymentMethod.find('#card_number, #card_expiry, #card_code').removeClass('error');
65
+ if (Spree.stripeElementsPaymentMethod.is(':visible')) {
66
+ expiration = $('.cardExpiry:visible').payment('cardExpiryVal');
67
+ params = $.extend({
68
+ number: $('.cardNumber:visible').val(),
69
+ cvc: $('.cardCode:visible').val(),
70
+ exp_month: expiration.month || 0,
71
+ exp_year: expiration.year || 0
72
+ }, Spree.stripeAdditionalInfo);
73
+ Stripe.card.createToken(params, stripeElementResponseHandler);
74
+ return false;
75
+ }
76
+ });
77
+ });
78
+ });
79
+ </script>
@@ -0,0 +1,21 @@
1
+ <fieldset data-hook="bank_transfer">
2
+ <legend><%= Spree.t(:bank_transfer) %></legend>
3
+ <table class="table table-condensed table-bordered">
4
+ <tr>
5
+ <th width="20%"><%= Spree.t('stripe.ach.account_holder_name') %>:</th>
6
+ <td><%= payment.source.account_holder_name %></td>
7
+ </tr>
8
+ <tr>
9
+ <th><%= Spree.t('stripe.ach.account_holder_type') %>:</th>
10
+ <td><%= payment.source.account_holder_type %></td>
11
+ </tr>
12
+ <tr>
13
+ <th><%= Spree.t('stripe.ach.routing_number') %>:</th>
14
+ <td><%= payment.source.routing_number %></td>
15
+ </tr>
16
+ <tr>
17
+ <th><%= Spree.t('stripe.ach.account_number') %>:</th>
18
+ <td><%= payment.source.account_number %></td>
19
+ </tr>
20
+ </table>
21
+ </fieldset>
@@ -0,0 +1 @@
1
+ <%= render "spree/admin/payments/source_views/gateway", payment: payment %>
@@ -0,0 +1 @@
1
+ <%= render "spree/admin/payments/source_views/gateway", payment: payment %>
@@ -0,0 +1,81 @@
1
+ <div class="payment-gateway">
2
+ <% param_prefix = "payment_source[#{payment_method.id}]" %>
3
+ <div class="payment-gateway-half-fields">
4
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field">
5
+ <%= text_field_tag "#{param_prefix}[account_holder_name]", "#{@order.bill_address_firstname} #{@order.bill_address_lastname}", { id: "account_holder_name", class: 'spree-flat-input', placeholder: Spree.t('stripe.ach.account_holder_name')} %>
6
+ </div>
7
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field">
8
+ <%= select_tag "#{param_prefix}[account_holder_type]", options_for_select(%w(Individual Company)), {id: "account_holder_type", class: 'spree-flat-input', placeholder: Spree.t('stripe.ach.account_holder_type')} %>
9
+ </div>
10
+
11
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field" data-hook="account_number">
12
+ <%= text_field_tag "#{param_prefix}[routing_number]", '', {id: 'routing_number', class: 'spree-flat-input', autocomplete: "off", placeholder: Spree.t('stripe.ach.routing_number'), maxlength: 9} %>
13
+ </div>
14
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field" data-hook="account_number">
15
+ <%= text_field_tag "#{param_prefix}[account_number]", '', {id: 'account_number', class: 'spree-flat-input', autocomplete: "off", placeholder: Spree.t('stripe.ach.account_number')} %>
16
+ </div>
17
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field" data-hook="account_number">
18
+ <%= text_field_tag "#{param_prefix}[verify_account_number]", '', {id: 'verify_account_number', class: 'spree-flat-input', autocomplete: "off", placeholder: Spree.t('stripe.ach.verify_account_number')} %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <script type="text/javascript" src="https://js.stripe.com/v3/"></script>
24
+ <script type="text/javascript">
25
+ var stripe = Stripe("<%= payment_method.preferred_publishable_key %>");
26
+ </script>
27
+
28
+ <script>
29
+ stripeResponseHandler = function(response) {
30
+ var paymentMethodId, token, bank_acc_status;
31
+ if (response.error) {
32
+ $('#stripeError').html(response.error.message);
33
+ var param_map = {
34
+ account_holder_name: '#account_holder_name',
35
+ account_holder_type: '#account_holder_type',
36
+ account_number: '#account_number',
37
+ routing_number: '#routing_number'
38
+ }
39
+ if (response.error.param){
40
+ errorField = Spree.stripePaymentMethod.find(param_map[response.error.param])
41
+ errorField.addClass('error');
42
+ errorField.parent().addClass('has-error');
43
+ }
44
+ return $('#stripeError').show();
45
+ } else {
46
+ token = response.token['id'];
47
+ bank_acc_status = response.token.bank_account['status'];
48
+ paymentMethodId = Spree.stripePaymentMethod.prop('id').split("_")[2];
49
+ Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][gateway_payment_profile_id]' value='" + token + "'/>");
50
+ Spree.stripePaymentMethod.append("<input type='hidden' class='stripeStatus' name='payment_source[" + paymentMethodId + "][status]' value='" + bank_acc_status + "'/>")
51
+ return Spree.stripePaymentMethod.parents("form").trigger('submit');
52
+ }
53
+ };
54
+
55
+ window.addEventListener('DOMContentLoaded', function () {
56
+ Spree.stripePaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
57
+ Spree.stripePaymentMethod.prepend("<div id='stripeError' class='errorExplanation alert alert-danger' style='display:none'></div>");
58
+
59
+ $('#checkout_form_payment [data-hook=buttons]').click(function (e) {
60
+ var params;
61
+ $('#stripeError').hide();
62
+ Spree.stripePaymentMethod.find('#account_holder_name, #account_holder_type, #account_number, #routing_number').removeClass('error');
63
+ if (Spree.stripePaymentMethod.is(':visible')) {
64
+ e.preventDefault();
65
+
66
+ params = $.extend({
67
+ country: 'US',
68
+ currency: 'usd',
69
+ account_holder_name: $('#account_holder_name:visible').val(),
70
+ account_holder_type: $('#account_holder_type:visible').val(),
71
+ routing_number: $('#routing_number:visible').val(),
72
+ account_number: $('#account_number:visible').val()
73
+ }, Spree.stripeAdditionalInfo);
74
+
75
+ stripe.createToken('bank_account', params).then(stripeResponseHandler);
76
+
77
+ return false;
78
+ }
79
+ });
80
+ });
81
+ </script>
@@ -0,0 +1,16 @@
1
+ <p class="payment-type checkout-content-header">
2
+ <%= Spree.t('stripe.ach.verify_bank_account').upcase %>
3
+ </p>
4
+
5
+ <div class="payment-gateway">
6
+ <div class="payment-gateway-half-fields">
7
+ <input type="hidden" name="order[payment_id]" value= <%= payment.id %> />
8
+ <input type='hidden' name='verifyAch' value=true />
9
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field">
10
+ <%= number_field_tag "amounts[]", nil, { id: "deposit1", class: 'spree-flat-input', placeholder: Spree.t('stripe.ach.first_deposit') } %>
11
+ </div>
12
+ <div class="mb-4 payment-gateway-field checkout-content-inner-field">
13
+ <%= number_field_tag "amounts[]", nil, { id: "deposit2", class: 'spree-flat-input', placeholder: Spree.t('stripe.ach.second_deposit') } %>
14
+ </div>
15
+ </div>
16
+ </div>
@@ -9,7 +9,6 @@
9
9
  <script>
10
10
  var stripeApplePay = Stripe("<%= payment_method.preferred_publishable_key %>");
11
11
  var elements = stripeApplePay.elements();
12
-
13
12
  var paymentRequest = stripeApplePay.paymentRequest({
14
13
  country: '<%= payment_method.preferred_country_code.try(:upcase) %>',
15
14
  currency: '<%= @order.currency.downcase %>',
@@ -64,6 +63,15 @@
64
63
  form.appendChild(hiddenInput);
65
64
  };
66
65
 
66
+ function hideApplePayRadioButtonForNonAppleDevices() {
67
+ var isSafari = !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);
68
+ var isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
69
+
70
+ if (!isSafari && !isiOS) {
71
+ $('input[name$="order[payments_attributes][][payment_method_id]"][value="<%= payment_method.id %>"]').closest('li').hide()
72
+ }
73
+ }
74
+
67
75
  paymentRequest.on('token', function(ev) {
68
76
  var form = document.getElementById('checkout_form_payment');
69
77
  var token = ev.token;
@@ -83,6 +91,7 @@
83
91
 
84
92
  window.addEventListener('DOMContentLoaded', function() {
85
93
  Spree.stripeApplePayPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
94
+ hideApplePayRadioButtonForNonAppleDevices();
86
95
 
87
96
  prButton.on('ready', function () {
88
97
  Spree.stripeApplePayPaymentMethod.prepend("<div id='stripeApplePayError' class='errorExplanation alert alert-danger' style='display:none'></div>");
@@ -1,24 +1,15 @@
1
1
  <% param_prefix = "payment_source[#{payment_method.id}]" %>
2
2
  <div class="well clearfix">
3
3
  <p class="field">
4
- <%= label_tag "name_on_card_#{payment_method.id}" do %>
5
- <%= Spree.t(:name_on_card) %><abbr class="required" title="required">*</abbr>
6
- <% end %>
7
- <%= text_field_tag "#{param_prefix}[name]", "#{@order.bill_address_firstname} #{@order.bill_address_lastname}", { id: "name_on_card_#{payment_method.id}", class: 'form-control required'} %>
8
- </p>
9
- <div class="form-row">
10
- <%= label_tag "card_number" do %>
11
- <%= Spree.t(:card_number) %><abbr class="required" title="required">*</abbr>
12
- <% end %>
13
- <div class="form-control required cardNumber">
14
- <div id="card-element">
15
- <!-- a Stripe Element will be inserted here. -->
16
- </div>
17
- </div>
18
- <!-- Used to display form errors -->
19
- <div id="card-errors" role="alert"></div>
20
- </div>
4
+ <%= text_field_tag "#{param_prefix}[name]", "#{@order.bill_address_firstname} #{@order.bill_address_lastname}", { id: "name_on_card_#{payment_method.id}", class: 'spree-flat-input', placeholder: Spree.t(:name_on_card)} %>
21
5
  </p>
6
+ <div class="form-control required cardNumber spree-flat-input">
7
+ <div id="card-element">
8
+ <!-- a Stripe Element will be inserted here. -->
9
+ </div>
10
+ </div>
11
+ <!-- Used to display form errors -->
12
+ <div id="card-errors" role="alert"></div>
22
13
  </div>
23
14
 
24
15
  <script type="text/javascript" src="https://js.stripe.com/v3/"></script>
@@ -26,20 +17,24 @@
26
17
  <script>
27
18
  var stripeElements = Stripe("<%= payment_method.preferred_publishable_key %>");
28
19
  var elements = stripeElements.elements();
20
+ var spreeFlatInputStyle = getComputedStyle(document.querySelector('input.spree-flat-input'));
29
21
 
30
22
  var card = elements.create('card', {
31
23
  iconStyle: 'solid',
32
24
  hidePostalCode: true,
33
25
  style: {
34
26
  base: {
35
- iconColor: '#555555',
36
- lineHeight: '24px',
37
- fontWeight: 300,
38
- fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
39
- fontSize: '14px',
27
+ color: spreeFlatInputStyle['color'],
28
+ iconColor: spreeFlatInputStyle['color'],
29
+ lineHeight: spreeFlatInputStyle['line-height'],
30
+ fontWeight: spreeFlatInputStyle['font-weight'],
31
+ fontFamily: spreeFlatInputStyle['font-family'],
32
+ fontSize: spreeFlatInputStyle['font-size'],
40
33
 
41
34
  '::placeholder': {
42
- color: '#555555',
35
+ color: '#757575',
36
+ fontWeight: 300,
37
+ textTransform: 'uppercase'
43
38
  },
44
39
  },
45
40
  invalid: {
@@ -0,0 +1,10 @@
1
+ FactoryBot.define do
2
+ factory :check, class: Spree::Check do
3
+ account_holder_name { 'John Doe' }
4
+ account_holder_type { 'Individual' }
5
+ account_type { 'checking' }
6
+ routing_number { '110000000' }
7
+ account_number { '000123456789' }
8
+ association(:payment_method, factory: :credit_card_payment_method)
9
+ end
10
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Admin Panel Stripe elements payment', type: :feature, :js => true do
4
+ stub_authorization!
5
+
6
+ let!(:country) { create(:country, :states_required => true) }
7
+ let!(:state) { create(:state, :country => country) }
8
+ let!(:shipping_method) { create(:shipping_method) }
9
+ let!(:stock_location) { create(:stock_location) }
10
+ let!(:mug) { create(:product, :name => 'RoR Mug') }
11
+ let!(:zone) { create(:zone) }
12
+ let!(:stripe_elements_payment_method) do
13
+ Spree::Gateway::StripeElementsGateway.create!(
14
+ :name => 'Stripe Element',
15
+ :preferred_secret_key => 'sk_test_VCZnDv3GLU15TRvn8i2EsaAN',
16
+ :preferred_publishable_key => 'pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg'
17
+ )
18
+ end
19
+
20
+ let!(:order) { OrderWalkthrough.up_to(:payment) }
21
+ before { visit spree.new_admin_order_payment_path(order.number) }
22
+
23
+ it 'can process a valid payment' do
24
+ fill_in_stripe_payment
25
+ wait_for { !page.has_current_path?(spree.admin_order_payments_path(order.number)) }
26
+
27
+ expect(page.body).to have_content('Payment has been successfully created!')
28
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
29
+ end
30
+
31
+ if Spree.version.to_f >= 4.1
32
+ it 'shows an error with an invalid card name' do
33
+ fill_in_stripe_payment(true)
34
+
35
+ expect(page).to have_content("Credit card Name can't be blank")
36
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
37
+ end
38
+ else
39
+ it 'can proces valid payment with invalid card name' do
40
+ fill_in_stripe_payment(true)
41
+ wait_for { !page.has_current_path?(spree.admin_order_payments_path(order.number)) }
42
+
43
+ expect(page.body).to have_content('Payment has been successfully created!')
44
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
45
+ end
46
+ end
47
+
48
+ it 'shows an error with an invalid card number' do
49
+ fill_in_stripe_payment(false, true)
50
+
51
+ expect(page).to have_content('The card number is not a valid credit card number.')
52
+ expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
53
+ end
54
+
55
+ it 'shows an error with an invalid card code' do
56
+ fill_in_stripe_payment(false, false, true)
57
+
58
+ expect(page).to have_content("Your card's security code is invalid.")
59
+ expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
60
+ end
61
+
62
+ it 'shows an error with an invalid card expiration' do
63
+ fill_in_stripe_payment(false, false, false, true)
64
+
65
+ if Spree.version.to_f >= 4.1
66
+ expect(page).to have_content('Credit card Month is not a number')
67
+ expect(page).to have_content('Credit card Year is not a number')
68
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
69
+ else
70
+ expect(page).to have_content("Your card's expiration year is invalid.")
71
+ expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
72
+ end
73
+ end
74
+
75
+ def fill_in_stripe_payment(invalid_name = false, invalid_number = false, invalid_code = false, invalid_expiration = false)
76
+ fill_in 'Name *', with: invalid_name ? '' : 'Stripe Elements Gateway Payment'
77
+ fill_in_card_number(invalid_number)
78
+ fill_in_cvc(invalid_code)
79
+ fill_in_card_expiration(invalid_expiration)
80
+
81
+ click_button 'Update'
82
+ end
83
+
84
+ def fill_in_card_number(invalid_number)
85
+ number = invalid_number ? '123' : '4242 4242 4242 4242'
86
+ fill_in_field('Card Number *', '#card_number1', number)
87
+ end
88
+
89
+ def fill_in_card_expiration(invalid_expiration)
90
+ valid_expiry = Spree.version.to_f >= 4.2 ? "01/#{Time.current.year + 1}" : "01 / #{Time.current.year + 1}"
91
+ invalid_expiry = Spree.version.to_f >= 4.2 ? '01/' : '01 / '
92
+
93
+ card_expiry = invalid_expiration ? invalid_expiry : valid_expiry
94
+ fill_in_field('Expiration *', '#card_expiry1', card_expiry)
95
+ end
96
+
97
+ def fill_in_cvc(invalid_code)
98
+ value = invalid_code ? '1' : '123'
99
+ label = Spree.version.to_f >= 4.2 ? 'Card Varification Code (CVC) *' : 'Card Code *'
100
+
101
+ fill_in label, with: value
102
+ end
103
+
104
+ def fill_in_field(field_name, field_id, number)
105
+ until page.find(field_id).value == number
106
+ fill_in field_name, with: number
107
+ end
108
+ end
109
+ end
@@ -91,7 +91,10 @@ describe "Stripe checkout", type: :feature do
91
91
  expect(page).to have_css('.has-error #card_code.error')
92
92
  end
93
93
 
94
+ # this scenario will not occur on Spree 4.2 due to swapping jquery.payment to cleave
95
+ # see https://github.com/spree/spree/pull/10363
94
96
  it "shows an error with invalid expiry month field", :js => true do
97
+ skip if Spree.version.to_f >= 4.2
95
98
  fill_in 'card_number', with: '4242 4242 4242 4242'
96
99
  fill_in 'card_expiry', :with => "00 / #{Time.now.year + 1}"
97
100
  fill_in 'card_code', with: '123'
@@ -0,0 +1,185 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Gateway::StripeAchGateway do
4
+ let(:secret_key) { 'key' }
5
+ let(:email) { 'customer@example.com' }
6
+ let(:source) { Spree::Check.new }
7
+ let(:payment) {
8
+ double('Spree::Payment',
9
+ source: source,
10
+ order: double('Spree::Order',
11
+ email: email,
12
+ bill_address: bill_address,
13
+ user: double('Spree::User',
14
+ email: email)
15
+ )
16
+ )
17
+ }
18
+ let(:provider) do
19
+ double('provider').tap do |p|
20
+ p.stub(:purchase)
21
+ p.stub(:authorize)
22
+ p.stub(:capture)
23
+ p.stub(:verify)
24
+ end
25
+ end
26
+
27
+ before do
28
+ subject.preferences = { secret_key: secret_key }
29
+ subject.stub(:options_for_purchase_or_auth).and_return(['money','check','opts'])
30
+ subject.stub(:provider).and_return provider
31
+ end
32
+
33
+ describe '#create_profile' do
34
+ before do
35
+ payment.source.stub(:update!)
36
+ end
37
+
38
+ context 'with an order that has a bill address' do
39
+ let(:bill_address) {
40
+ double('Spree::Address',
41
+ address1: '123 Happy Road',
42
+ address2: 'Apt 303',
43
+ city: 'Suzarac',
44
+ zipcode: '95671',
45
+ state: double('Spree::State', name: 'Oregon'),
46
+ country: double('Spree::Country', name: 'United States')
47
+ )
48
+ }
49
+
50
+ it 'stores the bill address with the provider' do
51
+ subject.provider.should_receive(:store).with(payment.source, {
52
+ email: email,
53
+ login: secret_key,
54
+
55
+ address: {
56
+ address1: '123 Happy Road',
57
+ address2: 'Apt 303',
58
+ city: 'Suzarac',
59
+ zip: '95671',
60
+ state: 'Oregon',
61
+ country: 'United States'
62
+ }
63
+ }).and_return double.as_null_object
64
+
65
+ subject.create_profile payment
66
+ end
67
+ end
68
+
69
+ context 'with an order that does not have a bill address' do
70
+ let(:bill_address) { nil }
71
+
72
+ it 'does not store a bill address with the provider' do
73
+ subject.provider.should_receive(:store).with(payment.source, {
74
+ email: email,
75
+ login: secret_key
76
+ }).and_return double.as_null_object
77
+
78
+ subject.create_profile payment
79
+ end
80
+
81
+ end
82
+
83
+ context 'with a check represents payment_profile' do
84
+ let(:source) { Spree::Check.new(gateway_payment_profile_id: 'tok_profileid') }
85
+ let(:bill_address) { nil }
86
+
87
+ it 'stores the profile_id as a check' do
88
+ subject.provider.should_receive(:store).with(source.gateway_payment_profile_id, anything).and_return double.as_null_object
89
+
90
+ subject.create_profile payment
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'purchasing' do
96
+ after do
97
+ subject.purchase(19.99, 'check', {})
98
+ end
99
+
100
+ it 'send the payment to the provider' do
101
+ provider.should_receive(:purchase).with('money', 'check', 'opts')
102
+ end
103
+ end
104
+
105
+ context 'authorizing' do
106
+ after do
107
+ subject.authorize(19.99, 'check', {})
108
+ end
109
+
110
+ it 'send the authorization to the provider' do
111
+ provider.should_receive(:authorize).with('money', 'check', 'opts')
112
+ end
113
+ end
114
+
115
+ context 'verifying' do
116
+ after do
117
+ subject.verify(source, {})
118
+ end
119
+
120
+ it 'send the verify to the provider' do
121
+ provider.should_receive(:verify).with(source, anything)
122
+ end
123
+ end
124
+
125
+ context 'capturing' do
126
+
127
+ after do
128
+ subject.capture(1234, 'response_code', {})
129
+ end
130
+
131
+ it 'convert the amount to cents' do
132
+ provider.should_receive(:capture).with(1234, anything, anything)
133
+ end
134
+
135
+ it 'use the response code as the authorization' do
136
+ provider.should_receive(:capture).with(anything, 'response_code', anything)
137
+ end
138
+ end
139
+
140
+ context 'capture with payment class' do
141
+ let(:gateway) do
142
+ gateway = described_class.new(active: true)
143
+ gateway.set_preference :secret_key, secret_key
144
+ gateway.stub(:options_for_purchase_or_auth).and_return(['money', 'check', 'opts'])
145
+ gateway.stub(:provider).and_return provider
146
+ gateway.stub source_required: true
147
+ gateway
148
+ end
149
+
150
+ let(:order) { Spree::Order.create }
151
+
152
+ let(:check) do
153
+ # mock_model(Spree::Check, :gateway_customer_profile_id => 'cus_abcde',
154
+ # :imported => false)
155
+ create :check, gateway_customer_profile_id: 'cus_abcde', imported: false
156
+ end
157
+
158
+ let(:payment) do
159
+ payment = Spree::Payment.new
160
+ payment.source = check
161
+ payment.order = order
162
+ payment.payment_method = gateway
163
+ payment.amount = 98.55
164
+ payment.state = 'pending'
165
+ payment.response_code = '12345'
166
+ payment
167
+ end
168
+
169
+ after do
170
+ payment.capture!
171
+ end
172
+
173
+ let!(:success_response) do
174
+ double('success_response',
175
+ success?: true,
176
+ authorization: '123',
177
+ avs_result: { 'code' => 'avs-code' },
178
+ cvv_result: { 'code' => 'cvv-code', 'message' => 'CVV Result' })
179
+ end
180
+
181
+ it 'gets correct amount' do
182
+ provider.should_receive(:capture).with(9855, '12345', anything).and_return(success_response)
183
+ end
184
+ end
185
+ end