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.
- checksums.yaml +4 -4
- data/.travis.yml +31 -38
- data/Appraisals +4 -10
- data/README.md +9 -20
- data/app/models/spree/check.rb +41 -0
- data/app/models/spree/gateway/stripe_ach_gateway.rb +60 -0
- data/app/models/spree/payment_decorator.rb +29 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -0
- data/config/locales/en.yml +19 -0
- data/db/migrate/20200317135551_add_spree_check_payment_source.rb +22 -0
- data/gemfiles/{spree_3_2.gemfile → spree_4_1.gemfile} +1 -1
- data/gemfiles/{spree_3_5.gemfile → spree_4_2.gemfile} +1 -1
- data/lib/active_merchant/billing/stripe_gateway_decorator.rb +13 -0
- data/lib/controllers/spree/apple_pay_domain_verification_controller.rb +1 -1
- data/lib/spree_gateway/engine.rb +1 -0
- data/lib/spree_gateway/version.rb +1 -1
- data/lib/views/backend/spree/admin/payments/source_forms/_stripe_ach.html.erb +86 -0
- data/lib/views/backend/spree/admin/payments/source_forms/_stripe_apple_pay.html.erb +0 -0
- data/lib/views/backend/spree/admin/payments/source_forms/_stripe_elements.html.erb +79 -0
- data/lib/views/backend/spree/admin/payments/source_views/_stripe_ach.html.erb +21 -0
- data/lib/views/backend/spree/admin/payments/source_views/_stripe_apple_pay.html.erb +1 -0
- data/lib/views/backend/spree/admin/payments/source_views/_stripe_elements.html.erb +1 -0
- data/lib/views/frontend/spree/checkout/payment/_stripe_ach.html.erb +81 -0
- data/lib/views/frontend/spree/checkout/payment/_stripe_ach_verify.html.erb +16 -0
- data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +10 -1
- data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +18 -23
- data/spec/factories/check_factory.rb +10 -0
- data/spec/features/admin/stripe_elements_payment_spec.rb +109 -0
- data/spec/features/stripe_checkout_spec.rb +3 -0
- data/spec/models/gateway/stripe_ach_gateway_spec.rb +185 -0
- data/spec/spec_helper.rb +7 -64
- data/spree_gateway.gemspec +1 -20
- metadata +25 -274
- data/gemfiles/spree_3_7.gemfile +0 -9
- data/gemfiles/spree_4_0.gemfile +0 -8
File without changes
|
@@ -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
|
-
<%=
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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: '#
|
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
|