archetype_spree_gateway 3.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +39 -0
- data/Gemfile +9 -0
- data/Guardfile +9 -0
- data/LICENSE.md +26 -0
- data/README.md +105 -0
- data/Rakefile +15 -0
- data/app/models/spree/billing_integration.rb +21 -0
- data/app/models/spree/check.rb +41 -0
- data/app/models/spree/gateway/authorize_net.rb +45 -0
- data/app/models/spree/gateway/authorize_net_cim.rb +213 -0
- data/app/models/spree/gateway/balanced_gateway.rb +64 -0
- data/app/models/spree/gateway/banwire.rb +15 -0
- data/app/models/spree/gateway/beanstream.rb +193 -0
- data/app/models/spree/gateway/braintree_gateway.rb +184 -0
- data/app/models/spree/gateway/card_save.rb +10 -0
- data/app/models/spree/gateway/cyber_source.rb +10 -0
- data/app/models/spree/gateway/data_cash.rb +10 -0
- data/app/models/spree/gateway/epay.rb +10 -0
- data/app/models/spree/gateway/eway.rb +18 -0
- data/app/models/spree/gateway/eway_rapid.rb +14 -0
- data/app/models/spree/gateway/maxipago.rb +14 -0
- data/app/models/spree/gateway/migs.rb +11 -0
- data/app/models/spree/gateway/moneris.rb +10 -0
- data/app/models/spree/gateway/pay_junction.rb +14 -0
- data/app/models/spree/gateway/pay_pal_gateway.rb +12 -0
- data/app/models/spree/gateway/payflow_pro.rb +15 -0
- data/app/models/spree/gateway/paymill.rb +12 -0
- data/app/models/spree/gateway/payu_polska_gateway.rb +24 -0
- data/app/models/spree/gateway/pin_gateway.rb +60 -0
- data/app/models/spree/gateway/quickpay.rb +9 -0
- data/app/models/spree/gateway/sage_pay.rb +11 -0
- data/app/models/spree/gateway/secure_pay_au.rb +10 -0
- data/app/models/spree/gateway/spreedly_core_gateway.rb +11 -0
- data/app/models/spree/gateway/stripe_ach_gateway.rb +60 -0
- data/app/models/spree/gateway/stripe_apple_pay_gateway.rb +10 -0
- data/app/models/spree/gateway/stripe_elements_gateway.rb +61 -0
- data/app/models/spree/gateway/stripe_gateway.rb +151 -0
- data/app/models/spree/gateway/usa_epay_transaction.rb +9 -0
- data/app/models/spree/gateway/worldpay.rb +91 -0
- data/app/models/spree/payu_polska.rb +41 -0
- data/app/models/spree_gateway/apple_pay_order_decorator.rb +20 -0
- data/app/models/spree_gateway/apple_pay_payment_decorator.rb +9 -0
- data/app/models/spree_gateway/credit_card_decorator.rb +10 -0
- data/app/models/spree_gateway/order_decorator.rb +28 -0
- data/app/models/spree_gateway/payment_decorator.rb +36 -0
- data/app/views/spree/checkout/_payment_confirm.html.erb +39 -0
- data/app/views/spree/checkout/_payu.html +12 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/initializers/spree_permitted_attributes.rb +5 -0
- data/config/locales/bg.yml +4 -0
- data/config/locales/de.yml +4 -0
- data/config/locales/en.yml +47 -0
- data/config/locales/sv.yml +4 -0
- data/config/routes.rb +19 -0
- data/db/migrate/20200317135551_add_spree_check_payment_source.rb +22 -0
- data/db/migrate/20200422114908_add_intent_key_to_payment.rb +5 -0
- data/lib/active_merchant/billing/stripe_gateway_decorator.rb +33 -0
- data/lib/controllers/spree/api/v2/storefront/intents_controller.rb +45 -0
- data/lib/controllers/spree/apple_pay_domain_verification_controller.rb +11 -0
- data/lib/generators/spree_gateway/install/install_generator.rb +19 -0
- data/lib/spree_frontend/controllers/spree/checkout_controller_decorator.rb +19 -0
- data/lib/spree_gateway/engine.rb +80 -0
- data/lib/spree_gateway/version.rb +5 -0
- data/lib/spree_gateway.rb +4 -0
- data/lib/views/backend/spree/admin/log_entries/_braintree.html.erb +31 -0
- data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +28 -0
- data/lib/views/backend/spree/admin/payments/source_forms/_quickcheckout.html.erb +8 -0
- data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +79 -0
- 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.html.erb +1 -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.html.erb +86 -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_additional_info.html.erb +16 -0
- data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +109 -0
- data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +110 -0
- data/script/rails +7 -0
- data/spec/factories/check_factory.rb +10 -0
- data/spec/features/admin/stripe_elements_payment_spec.rb +100 -0
- data/spec/features/stripe_checkout_spec.rb +130 -0
- data/spec/features/stripe_elements_3ds_checkout_spec.rb +225 -0
- data/spec/models/gateway/authorize_net_cim_spec.rb +29 -0
- data/spec/models/gateway/authorize_net_spec.rb +23 -0
- data/spec/models/gateway/balanced_gateway_spec.rb +17 -0
- data/spec/models/gateway/banwire_spec.rb +11 -0
- data/spec/models/gateway/beanstream_spec.rb +17 -0
- data/spec/models/gateway/braintree_gateway_spec.rb +420 -0
- data/spec/models/gateway/card_save_spec.rb +11 -0
- data/spec/models/gateway/cyber_source_spec.rb +11 -0
- data/spec/models/gateway/data_cash_spec.rb +11 -0
- data/spec/models/gateway/epay_spec.rb +11 -0
- data/spec/models/gateway/eway_rapid_spec.rb +23 -0
- data/spec/models/gateway/eway_spec.rb +29 -0
- data/spec/models/gateway/maxipago_spec.rb +17 -0
- data/spec/models/gateway/moneris_spec.rb +11 -0
- data/spec/models/gateway/pay_junction_spec.rb +23 -0
- data/spec/models/gateway/pay_pal_spec.rb +11 -0
- data/spec/models/gateway/payflow_pro_spec.rb +23 -0
- data/spec/models/gateway/paymill_spec.rb +11 -0
- data/spec/models/gateway/pin_gateway_spec.rb +54 -0
- data/spec/models/gateway/quickpay_spec.rb +11 -0
- data/spec/models/gateway/sage_pay_spec.rb +11 -0
- data/spec/models/gateway/secure_pay_au_spec.rb +11 -0
- data/spec/models/gateway/stripe_ach_gateway_spec.rb +186 -0
- data/spec/models/gateway/stripe_gateway_spec.rb +199 -0
- data/spec/models/gateway/usa_epay_transaction_spec.rb +49 -0
- data/spec/models/gateway/worldpay_spec.rb +11 -0
- data/spec/models/spree/order_spec.rb +79 -0
- data/spec/requests/apple_pay_domain_verification.rb +45 -0
- data/spec/requests/spree/api/v2/storefront/intents_controller_spec.rb +198 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/order_walktrough.rb +73 -0
- data/spec/support/wait_for_stripe.rb +27 -0
- data/spec/support/within_stripe_3ds_popup.rb +10 -0
- data/spree_gateway.gemspec +37 -0
- metadata +277 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
<%= render "spree/checkout/payment/gateway", payment_method: payment_method %>
|
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, stripeResponseHandler;
|
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
|
+
|
27
|
+
stripeResponseHandler = function(status, response) {
|
28
|
+
var paymentMethodId, token;
|
29
|
+
if (response.error) {
|
30
|
+
$('#stripeError').html(response.error.message);
|
31
|
+
var param_map = {
|
32
|
+
number: '#card_number',
|
33
|
+
exp_month: '#card_expiry',
|
34
|
+
exp_year: '#card_expiry',
|
35
|
+
cvc: '#card_code'
|
36
|
+
}
|
37
|
+
if (response.error.param) {
|
38
|
+
errorField = Spree.stripePaymentMethod.find(param_map[response.error.param])
|
39
|
+
errorField.addClass('error');
|
40
|
+
errorField.parent().addClass('has-error');
|
41
|
+
}
|
42
|
+
|
43
|
+
return $('#stripeError').show();
|
44
|
+
} else {
|
45
|
+
Spree.stripePaymentMethod.find('#card_number, #card_expiry, #card_code').prop("disabled", true);
|
46
|
+
Spree.stripePaymentMethod.find(".ccType").prop("disabled", false);
|
47
|
+
Spree.stripePaymentMethod.find(".ccType").val(mapCC(response.card.brand));
|
48
|
+
token = response['id'];
|
49
|
+
paymentMethodId = Spree.stripePaymentMethod.prop('id').split("_")[2];
|
50
|
+
Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][gateway_payment_profile_id]' value='" + token + "'/>");
|
51
|
+
Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][last_digits]' value='" + response.card.last4 + "'/>");
|
52
|
+
Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][month]' value='" + response.card.exp_month + "'/>");
|
53
|
+
Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId + "][year]' value='" + response.card.exp_year + "'/>");
|
54
|
+
return Spree.stripePaymentMethod.parents("form").trigger('submit');
|
55
|
+
}
|
56
|
+
};
|
57
|
+
|
58
|
+
window.addEventListener('DOMContentLoaded', function () {
|
59
|
+
Spree.stripePaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
|
60
|
+
Spree.stripePaymentMethod.prepend("<div id='stripeError' class='errorExplanation alert alert-danger' style='display:none'></div>");
|
61
|
+
|
62
|
+
$('#checkout_form_payment [data-hook=buttons]').click(function (e) {
|
63
|
+
|
64
|
+
var expiration, params;
|
65
|
+
$('#stripeError').hide();
|
66
|
+
Spree.stripePaymentMethod.find('#card_number, #card_expiry, #card_code').removeClass('error');
|
67
|
+
if (Spree.stripePaymentMethod.is(':visible')) {
|
68
|
+
e.preventDefault();
|
69
|
+
|
70
|
+
expiration = $('.cardExpiry:visible').payment('cardExpiryVal');
|
71
|
+
params = $.extend({
|
72
|
+
number: $('.cardNumber:visible').val(),
|
73
|
+
cvc: $('.cardCode:visible').val(),
|
74
|
+
exp_month: expiration.month || 0,
|
75
|
+
exp_year: expiration.year || 0
|
76
|
+
}, Spree.stripeAdditionalInfo);
|
77
|
+
|
78
|
+
Stripe.card.createToken(params, stripeResponseHandler);
|
79
|
+
|
80
|
+
return false;
|
81
|
+
}
|
82
|
+
});
|
83
|
+
});
|
84
|
+
</script>
|
85
|
+
|
86
|
+
<%= render 'spree/checkout/payment/stripe_additional_info', bill_address: @order.bill_address %>
|
@@ -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>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% bill_address ||= @order.bill_address %>
|
2
|
+
<%- if @order.has_checkout_step?('address') -%>
|
3
|
+
<script>
|
4
|
+
window.addEventListener('DOMContentLoaded', function() {
|
5
|
+
Spree.stripeAdditionalInfo = {
|
6
|
+
name: "<%= bill_address.full_name %>",
|
7
|
+
address_line1: "<%= bill_address.address1 %>",
|
8
|
+
address_line2: "<%= bill_address.address2 %>",
|
9
|
+
address_city: "<%= bill_address.city %>",
|
10
|
+
address_state: "<%= bill_address.state_text %>",
|
11
|
+
address_zip: "<%= bill_address.zipcode %>",
|
12
|
+
address_country: "<%= bill_address.country %>"
|
13
|
+
};
|
14
|
+
});
|
15
|
+
</script>
|
16
|
+
<%- end -%>
|
@@ -0,0 +1,109 @@
|
|
1
|
+
<% param_prefix = "payment_source[#{payment_method.id}]" %>
|
2
|
+
|
3
|
+
<div id="payment-request-button">
|
4
|
+
<!-- A Stripe Element will be inserted here. -->
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<script type="text/javascript" src="https://js.stripe.com/v3/"></script>
|
8
|
+
|
9
|
+
<script>
|
10
|
+
var stripeApplePay = Stripe("<%= payment_method.preferred_publishable_key %>");
|
11
|
+
var elements = stripeApplePay.elements();
|
12
|
+
var paymentRequest = stripeApplePay.paymentRequest({
|
13
|
+
country: '<%= payment_method.preferred_country_code.try(:upcase) %>',
|
14
|
+
currency: '<%= @order.currency.downcase %>',
|
15
|
+
displayItems: [
|
16
|
+
<% @order.line_items.each do |line_item| %>
|
17
|
+
{
|
18
|
+
label: '<%= line_item.name %> x <%= line_item.quantity %>',
|
19
|
+
amount: <%= Spree::Money.new(line_item.total, currency: line_item.currency).amount_in_cents %>
|
20
|
+
},
|
21
|
+
<% end %>
|
22
|
+
<% if @order.tax_total != 0 %>
|
23
|
+
{
|
24
|
+
label: '<%= Spree.t(:tax) %>',
|
25
|
+
amount: <%= Spree::Money.new(@order.tax_total, currency: @order.currency).amount_in_cents %>
|
26
|
+
},
|
27
|
+
<% end %>
|
28
|
+
<% if @order.shipment_total != 0 %>
|
29
|
+
{
|
30
|
+
label: '<%= Spree.t(:shipment) %>',
|
31
|
+
amount: <%= Spree::Money.new(@order.shipment_total, currency: @order.currency).amount_in_cents %>
|
32
|
+
}
|
33
|
+
<% end %>
|
34
|
+
],
|
35
|
+
total: {
|
36
|
+
label: '<%= Spree.t(:total) %>',
|
37
|
+
amount: <%= Spree::Money.new(@order.total, currency: @order.currency).amount_in_cents %>
|
38
|
+
},
|
39
|
+
requestPayerName: false,
|
40
|
+
requestPayerEmail: false,
|
41
|
+
requestPayerPhone: false
|
42
|
+
});
|
43
|
+
|
44
|
+
var prButton = elements.create('paymentRequestButton', {
|
45
|
+
paymentRequest: paymentRequest,
|
46
|
+
});
|
47
|
+
|
48
|
+
// Check the availability of the Payment Request API first.
|
49
|
+
paymentRequest.canMakePayment().then(function(result) {
|
50
|
+
if (result) {
|
51
|
+
prButton.mount('#payment-request-button');
|
52
|
+
} else {
|
53
|
+
document.getElementById('payment-request-button').style.display = 'none';
|
54
|
+
}
|
55
|
+
});
|
56
|
+
|
57
|
+
function addCreditCardFieldToForm(form, name, value) {
|
58
|
+
var hiddenInput = document.createElement('input');
|
59
|
+
|
60
|
+
hiddenInput.setAttribute('type', 'hidden');
|
61
|
+
hiddenInput.setAttribute('name', name);
|
62
|
+
hiddenInput.setAttribute('value', value);
|
63
|
+
form.appendChild(hiddenInput);
|
64
|
+
};
|
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
|
+
|
75
|
+
paymentRequest.on('token', function(ev) {
|
76
|
+
var form = document.getElementById('checkout_form_payment');
|
77
|
+
var token = ev.token;
|
78
|
+
if (ev.payerName) {
|
79
|
+
var payerName = ev.payerName
|
80
|
+
} else if (Spree.stripeAdditionalInfo) {
|
81
|
+
var payerName = Spree.stripeAdditionalInfo.name
|
82
|
+
}
|
83
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[gateway_payment_profile_id]', token.id);
|
84
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[number]', token.card.last4);
|
85
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[month]', token.card.exp_month);
|
86
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[year]', token.card.exp_year);
|
87
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[name]', payerName);
|
88
|
+
ev.complete('success');
|
89
|
+
form.submit();
|
90
|
+
});
|
91
|
+
|
92
|
+
window.addEventListener('DOMContentLoaded', function() {
|
93
|
+
Spree.stripeApplePayPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
|
94
|
+
hideApplePayRadioButtonForNonAppleDevices();
|
95
|
+
|
96
|
+
prButton.on('ready', function () {
|
97
|
+
Spree.stripeApplePayPaymentMethod.prepend("<div id='stripeApplePayError' class='errorExplanation alert alert-danger' style='display:none'></div>");
|
98
|
+
var form = document.getElementById('checkout_form_payment');
|
99
|
+
form.addEventListener('submit', function(e) {
|
100
|
+
if (Spree.stripeApplePayPaymentMethod.is(':visible')) {
|
101
|
+
$('#stripeApplePayError').hide();
|
102
|
+
e.preventDefault();
|
103
|
+
}
|
104
|
+
});
|
105
|
+
});
|
106
|
+
});
|
107
|
+
</script>
|
108
|
+
|
109
|
+
<%= render 'spree/checkout/payment/stripe_additional_info' %>
|
@@ -0,0 +1,110 @@
|
|
1
|
+
<% param_prefix = "payment_source[#{payment_method.id}]" %>
|
2
|
+
<div class="well clearfix">
|
3
|
+
<p class="field">
|
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)} %>
|
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>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<script type="text/javascript" src="https://js.stripe.com/v3/"></script>
|
16
|
+
|
17
|
+
<script>
|
18
|
+
var stripeElements = Stripe("<%= payment_method.preferred_publishable_key %>");
|
19
|
+
var elements = stripeElements.elements();
|
20
|
+
var spreeFlatInputStyle = getComputedStyle(document.querySelector('input.spree-flat-input'));
|
21
|
+
|
22
|
+
var card = elements.create('card', {
|
23
|
+
iconStyle: 'solid',
|
24
|
+
hidePostalCode: true,
|
25
|
+
style: {
|
26
|
+
base: {
|
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'],
|
33
|
+
|
34
|
+
'::placeholder': {
|
35
|
+
color: '#757575',
|
36
|
+
fontWeight: 300,
|
37
|
+
textTransform: 'uppercase'
|
38
|
+
},
|
39
|
+
},
|
40
|
+
invalid: {
|
41
|
+
iconColor: '#e85746',
|
42
|
+
color: '#e85746',
|
43
|
+
}
|
44
|
+
},
|
45
|
+
classes: {
|
46
|
+
focus: 'is-focused',
|
47
|
+
empty: 'is-empty',
|
48
|
+
},
|
49
|
+
});
|
50
|
+
|
51
|
+
window.addEventListener('DOMContentLoaded', function () {
|
52
|
+
card.mount('#card-element');
|
53
|
+
|
54
|
+
card.on('ready', function () {
|
55
|
+
Spree.stripeElementsPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
|
56
|
+
Spree.stripeElementsPaymentMethod.prepend("<div id='stripeElementsError' class='errorExplanation alert alert-danger' style='display:none'></div>");
|
57
|
+
var form = document.getElementById('checkout_form_payment');
|
58
|
+
|
59
|
+
form.addEventListener('submit', function(e) {
|
60
|
+
if (Spree.stripeElementsPaymentMethod.is(':visible')) {
|
61
|
+
$('#stripeElementsError').hide();
|
62
|
+
e.preventDefault();
|
63
|
+
createStripeElementsToken();
|
64
|
+
}
|
65
|
+
});
|
66
|
+
card.addEventListener('change', function(event) {
|
67
|
+
if (!event.error) {
|
68
|
+
$(form).find('input[type="submit"]').prop('disabled', false)
|
69
|
+
}
|
70
|
+
});
|
71
|
+
});
|
72
|
+
});
|
73
|
+
|
74
|
+
function addCreditCardFieldToForm(form, name, value) {
|
75
|
+
var hiddenInput = document.createElement('input');
|
76
|
+
|
77
|
+
hiddenInput.setAttribute('type', 'hidden');
|
78
|
+
hiddenInput.setAttribute('name', name);
|
79
|
+
hiddenInput.setAttribute('value', value);
|
80
|
+
form.appendChild(hiddenInput);
|
81
|
+
};
|
82
|
+
|
83
|
+
function stripeElementsTokenHandler(token) {
|
84
|
+
var form = document.getElementById('checkout_form_payment');
|
85
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[gateway_payment_profile_id]', token.id)
|
86
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[number]', token.card.last4)
|
87
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[month]', token.card.exp_month)
|
88
|
+
addCreditCardFieldToForm(form, '<%= param_prefix %>[year]', token.card.exp_year)
|
89
|
+
form.submit();
|
90
|
+
};
|
91
|
+
|
92
|
+
function createStripeElementsToken() {
|
93
|
+
stripeElements.createToken(card, Spree.stripeAdditionalInfo).then(function (result) {
|
94
|
+
if (result.error) {
|
95
|
+
// Inform the user if there was an error
|
96
|
+
var errorElement = document.getElementById('card-errors');
|
97
|
+
|
98
|
+
$('#stripeElementsError').html(result.error.message);
|
99
|
+
$('#stripeElementsError').show()
|
100
|
+
Spree.enableSave();
|
101
|
+
} else {
|
102
|
+
stripeElementsTokenHandler(result.token);
|
103
|
+
}
|
104
|
+
});
|
105
|
+
};
|
106
|
+
|
107
|
+
</script>
|
108
|
+
|
109
|
+
<%= render 'spree/checkout/payment/stripe_additional_info' %>
|
110
|
+
|
data/script/rails
ADDED
@@ -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,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Admin Panel Stripe elements payment', type: :feature 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
|
+
stores: [::Spree::Store.default]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
let!(:order) { OrderWalkthrough.up_to(:payment) }
|
22
|
+
before { visit spree.new_admin_order_payment_path(order.number) }
|
23
|
+
|
24
|
+
it 'can process a valid payment' do
|
25
|
+
fill_in_stripe_payment
|
26
|
+
wait_for { !page.has_current_path?(spree.admin_order_payments_path(order.number)) }
|
27
|
+
|
28
|
+
expect(page.body).to have_content('Payment has been successfully created!')
|
29
|
+
expect(page).to have_current_path spree.admin_order_payments_path(order.number)
|
30
|
+
end
|
31
|
+
|
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
|
+
|
39
|
+
it 'shows an error with an invalid card number' do
|
40
|
+
fill_in_stripe_payment(false, true)
|
41
|
+
|
42
|
+
expect(page).to have_content('The card number is not a valid credit card number.')
|
43
|
+
expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'shows an error with an invalid card code' do
|
47
|
+
fill_in_stripe_payment(false, false, true)
|
48
|
+
|
49
|
+
expect(page).to have_content("Your card's security code is invalid.")
|
50
|
+
expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'shows an error with an invalid card expiration' do
|
54
|
+
fill_in_stripe_payment(false, false, false, true)
|
55
|
+
|
56
|
+
if Spree.version.to_f >= 4.1 || Spree.version.to_f >= 3.7
|
57
|
+
expect(page).to have_content('Credit card Month is not a number')
|
58
|
+
expect(page).to have_content('Credit card Year is not a number')
|
59
|
+
expect(page).to have_current_path spree.admin_order_payments_path(order.number)
|
60
|
+
else
|
61
|
+
expect(page).to have_content("Your card's expiration year is invalid.")
|
62
|
+
expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def fill_in_stripe_payment(invalid_name = false, invalid_number = false, invalid_code = false, invalid_expiration = false)
|
67
|
+
fill_in 'Name *', with: invalid_name ? '' : 'Stripe Elements Gateway Payment'
|
68
|
+
fill_in_card_number(invalid_number)
|
69
|
+
fill_in_cvc(invalid_code)
|
70
|
+
fill_in_card_expiration(invalid_expiration)
|
71
|
+
|
72
|
+
click_button 'Update'
|
73
|
+
end
|
74
|
+
|
75
|
+
def fill_in_card_number(invalid_number)
|
76
|
+
number = invalid_number ? '123' : '4242 4242 4242 4242'
|
77
|
+
fill_in_field('Card Number *', "#card_number#{stripe_elements_payment_method.id}", number)
|
78
|
+
end
|
79
|
+
|
80
|
+
def fill_in_card_expiration(invalid_expiration)
|
81
|
+
valid_expiry = Spree.version.to_f >= 4.2 ? "01/#{Time.current.year + 1}" : "01 / #{Time.current.year + 1}"
|
82
|
+
invalid_expiry = Spree.version.to_f >= 4.2 ? '01/' : '01 / '
|
83
|
+
|
84
|
+
card_expiry = invalid_expiration ? invalid_expiry : valid_expiry
|
85
|
+
fill_in_field('Expiration *', "#card_expiry#{stripe_elements_payment_method.id}", card_expiry)
|
86
|
+
end
|
87
|
+
|
88
|
+
def fill_in_cvc(invalid_code)
|
89
|
+
value = invalid_code ? '1' : '123'
|
90
|
+
label = Spree.version.to_f >= 4.2 ? 'Card Varification Code (CVC) *' : 'Card Code *'
|
91
|
+
|
92
|
+
fill_in label, with: value
|
93
|
+
end
|
94
|
+
|
95
|
+
def fill_in_field(field_name, field_id, number)
|
96
|
+
until page.find(field_id).value == number
|
97
|
+
fill_in field_name, with: number
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Stripe checkout", type: :feature, js: true do
|
4
|
+
let!(:country) { create(:country, :states_required => true) }
|
5
|
+
let!(:state) { create(:state, :country => country) }
|
6
|
+
let!(:shipping_method) { create(:shipping_method) }
|
7
|
+
let!(:stock_location) { create(:stock_location) }
|
8
|
+
let!(:mug) { create(:product, :name => "RoR Mug") }
|
9
|
+
let!(:stripe_payment_method) do
|
10
|
+
Spree::Gateway::StripeGateway.create!(
|
11
|
+
name: 'Stripe',
|
12
|
+
preferred_secret_key: 'sk_test_VCZnDv3GLU15TRvn8i2EsaAN',
|
13
|
+
preferred_publishable_key: 'pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg',
|
14
|
+
stores: [::Spree::Store.default]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
let!(:zone) { create(:zone) }
|
19
|
+
|
20
|
+
before do
|
21
|
+
if Spree.version.to_f >= 4.2
|
22
|
+
payment_method = Spree::PaymentMethod.first
|
23
|
+
payment_method.update!(stores: [Spree::Store.first])
|
24
|
+
end
|
25
|
+
|
26
|
+
user = create(:user)
|
27
|
+
|
28
|
+
order = OrderWalkthrough.up_to(:delivery)
|
29
|
+
order.stub :confirmation_required? => true
|
30
|
+
|
31
|
+
order.reload
|
32
|
+
order.user = user
|
33
|
+
order.update_with_updater!
|
34
|
+
|
35
|
+
Spree::CheckoutController.any_instance.stub(:current_order => order)
|
36
|
+
Spree::CheckoutController.any_instance.stub(:try_spree_current_user => user)
|
37
|
+
Spree::CheckoutController.any_instance.stub(:skip_state_validation? => true)
|
38
|
+
Spree::OrdersController.any_instance.stub(try_spree_current_user: user)
|
39
|
+
|
40
|
+
# Capybara should wait up to 10 seconds for async. changes to be applied
|
41
|
+
Capybara.default_max_wait_time = 10
|
42
|
+
|
43
|
+
visit spree.checkout_state_path(:payment)
|
44
|
+
begin
|
45
|
+
setup_stripe_watcher
|
46
|
+
rescue Capybara::NotSupportedByDriverError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# This will pass the CC data to the server and the StripeGateway class handles it
|
51
|
+
it "can process a valid payment (without JS)", js: false do
|
52
|
+
fill_in 'card_number', with: '4242 4242 4242 4242'
|
53
|
+
fill_in 'card_code', with: '123'
|
54
|
+
fill_in 'card_expiry', with: "01 / #{Time.current.year + 1}"
|
55
|
+
click_button "Save and Continue"
|
56
|
+
expect(page.current_url).to include("/checkout/confirm")
|
57
|
+
click_button "Place Order"
|
58
|
+
order = Spree::Order.complete.last
|
59
|
+
expect(page.current_url).to include("/orders/#{order.number}")
|
60
|
+
expect(page).to have_content(order.number)
|
61
|
+
end
|
62
|
+
|
63
|
+
# This will fetch a token from Stripe.com and then pass that to the webserver.
|
64
|
+
# The server then processes the payment using that token.
|
65
|
+
it "can process a valid payment (with JS)" do
|
66
|
+
fill_in_with_force('card_number', with: "4242424242424242")
|
67
|
+
fill_in_with_force('card_expiry', with: "01 / #{Time.current.year + 1}")
|
68
|
+
fill_in 'card_code', with: '123'
|
69
|
+
click_button "Save and Continue"
|
70
|
+
wait_for_stripe # Wait for Stripe API to return + form to submit
|
71
|
+
expect(page).to have_css('#checkout_form_confirm')
|
72
|
+
expect(page.current_url).to include("/checkout/confirm")
|
73
|
+
click_button "Place Order"
|
74
|
+
order = Spree::Order.complete.last
|
75
|
+
expect(page.current_url).to include("/orders/#{order.number}")
|
76
|
+
expect(page).to have_content(order.number)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "shows an error with an invalid credit card number" do
|
80
|
+
# Card number is NOT valid. Fails Luhn checksum
|
81
|
+
fill_in 'card_number', with: '4242 4242 4242 4249'
|
82
|
+
click_button "Save and Continue"
|
83
|
+
wait_for_stripe
|
84
|
+
if Spree.version.to_f >= 3.7 and Spree.version.to_f <= 4.1
|
85
|
+
expect(page).to have_content("The card number is not a valid credit card number")
|
86
|
+
end
|
87
|
+
if Spree.version.to_f >= 4.2
|
88
|
+
expect(page).to have_content("Your card number is incorrect")
|
89
|
+
expect(page).to have_css('.has-error #card_number.error')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "shows an error with invalid security fields" do
|
94
|
+
fill_in_with_force('card_number', with: "4242424242424242")
|
95
|
+
fill_in_with_force('card_expiry', with: "01 / #{Time.current.year + 1}")
|
96
|
+
fill_in 'card_code', with: '99'
|
97
|
+
click_button "Save and Continue"
|
98
|
+
wait_for_stripe
|
99
|
+
expect(page).to have_content("Your card's security code is invalid.")
|
100
|
+
expect(page).to have_css('.has-error #card_code.error')
|
101
|
+
end
|
102
|
+
|
103
|
+
# this scenario will not occur on Spree 4.2 due to swapping jquery.payment to cleave
|
104
|
+
# see https://github.com/spree/spree/pull/10363
|
105
|
+
it "shows an error with invalid expiry month field" do
|
106
|
+
skip if Spree.version.to_f >= 4.2
|
107
|
+
fill_in_with_force('card_number', with: "4242424242424242")
|
108
|
+
fill_in_with_force('card_expiry', with: "00 / #{Time.current.year + 1}")
|
109
|
+
fill_in 'card_code', with: '123'
|
110
|
+
click_button "Save and Continue"
|
111
|
+
wait_for_stripe
|
112
|
+
expect(page).to have_content("Your card's expiration month is invalid.")
|
113
|
+
expect(page).to have_css('.has-error #card_expiry.error')
|
114
|
+
end
|
115
|
+
|
116
|
+
it "shows an error with invalid expiry year field" do
|
117
|
+
fill_in_with_force('card_number', with: "4242424242424242")
|
118
|
+
fill_in_with_force('card_expiry', with: "12 / ")
|
119
|
+
fill_in 'card_code', with: '123'
|
120
|
+
click_button "Save and Continue"
|
121
|
+
wait_for_stripe
|
122
|
+
expect(page).to have_content("Your card's expiration year is invalid.")
|
123
|
+
expect(page).to have_css('.has-error #card_expiry.error')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def fill_in_with_force(locator, with:)
|
128
|
+
field_id = find_field(locator)[:id]
|
129
|
+
page.execute_script("document.getElementById('#{field_id}').value = '#{with}';")
|
130
|
+
end
|