spree_gateway 3.7.1 → 3.8.0

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