archetype_spree_gateway 3.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +39 -0
  5. data/Gemfile +9 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.md +26 -0
  8. data/README.md +105 -0
  9. data/Rakefile +15 -0
  10. data/app/models/spree/billing_integration.rb +21 -0
  11. data/app/models/spree/check.rb +41 -0
  12. data/app/models/spree/gateway/authorize_net.rb +45 -0
  13. data/app/models/spree/gateway/authorize_net_cim.rb +213 -0
  14. data/app/models/spree/gateway/balanced_gateway.rb +64 -0
  15. data/app/models/spree/gateway/banwire.rb +15 -0
  16. data/app/models/spree/gateway/beanstream.rb +193 -0
  17. data/app/models/spree/gateway/braintree_gateway.rb +184 -0
  18. data/app/models/spree/gateway/card_save.rb +10 -0
  19. data/app/models/spree/gateway/cyber_source.rb +10 -0
  20. data/app/models/spree/gateway/data_cash.rb +10 -0
  21. data/app/models/spree/gateway/epay.rb +10 -0
  22. data/app/models/spree/gateway/eway.rb +18 -0
  23. data/app/models/spree/gateway/eway_rapid.rb +14 -0
  24. data/app/models/spree/gateway/maxipago.rb +14 -0
  25. data/app/models/spree/gateway/migs.rb +11 -0
  26. data/app/models/spree/gateway/moneris.rb +10 -0
  27. data/app/models/spree/gateway/pay_junction.rb +14 -0
  28. data/app/models/spree/gateway/pay_pal_gateway.rb +12 -0
  29. data/app/models/spree/gateway/payflow_pro.rb +15 -0
  30. data/app/models/spree/gateway/paymill.rb +12 -0
  31. data/app/models/spree/gateway/payu_polska_gateway.rb +24 -0
  32. data/app/models/spree/gateway/pin_gateway.rb +60 -0
  33. data/app/models/spree/gateway/quickpay.rb +9 -0
  34. data/app/models/spree/gateway/sage_pay.rb +11 -0
  35. data/app/models/spree/gateway/secure_pay_au.rb +10 -0
  36. data/app/models/spree/gateway/spreedly_core_gateway.rb +11 -0
  37. data/app/models/spree/gateway/stripe_ach_gateway.rb +60 -0
  38. data/app/models/spree/gateway/stripe_apple_pay_gateway.rb +10 -0
  39. data/app/models/spree/gateway/stripe_elements_gateway.rb +61 -0
  40. data/app/models/spree/gateway/stripe_gateway.rb +151 -0
  41. data/app/models/spree/gateway/usa_epay_transaction.rb +9 -0
  42. data/app/models/spree/gateway/worldpay.rb +91 -0
  43. data/app/models/spree/payu_polska.rb +41 -0
  44. data/app/models/spree_gateway/apple_pay_order_decorator.rb +20 -0
  45. data/app/models/spree_gateway/apple_pay_payment_decorator.rb +9 -0
  46. data/app/models/spree_gateway/credit_card_decorator.rb +10 -0
  47. data/app/models/spree_gateway/order_decorator.rb +28 -0
  48. data/app/models/spree_gateway/payment_decorator.rb +36 -0
  49. data/app/views/spree/checkout/_payment_confirm.html.erb +39 -0
  50. data/app/views/spree/checkout/_payu.html +12 -0
  51. data/config/initializers/inflections.rb +3 -0
  52. data/config/initializers/spree_permitted_attributes.rb +5 -0
  53. data/config/locales/bg.yml +4 -0
  54. data/config/locales/de.yml +4 -0
  55. data/config/locales/en.yml +47 -0
  56. data/config/locales/sv.yml +4 -0
  57. data/config/routes.rb +19 -0
  58. data/db/migrate/20200317135551_add_spree_check_payment_source.rb +22 -0
  59. data/db/migrate/20200422114908_add_intent_key_to_payment.rb +5 -0
  60. data/lib/active_merchant/billing/stripe_gateway_decorator.rb +33 -0
  61. data/lib/controllers/spree/api/v2/storefront/intents_controller.rb +45 -0
  62. data/lib/controllers/spree/apple_pay_domain_verification_controller.rb +11 -0
  63. data/lib/generators/spree_gateway/install/install_generator.rb +19 -0
  64. data/lib/spree_frontend/controllers/spree/checkout_controller_decorator.rb +19 -0
  65. data/lib/spree_gateway/engine.rb +80 -0
  66. data/lib/spree_gateway/version.rb +5 -0
  67. data/lib/spree_gateway.rb +4 -0
  68. data/lib/views/backend/spree/admin/log_entries/_braintree.html.erb +31 -0
  69. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +28 -0
  70. data/lib/views/backend/spree/admin/payments/source_forms/_quickcheckout.html.erb +8 -0
  71. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +79 -0
  72. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_ach.html.erb +86 -0
  73. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_apple_pay.html.erb +0 -0
  74. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_elements.html.erb +79 -0
  75. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +1 -0
  76. data/lib/views/backend/spree/admin/payments/source_views/_stripe_ach.html.erb +21 -0
  77. data/lib/views/backend/spree/admin/payments/source_views/_stripe_apple_pay.html.erb +1 -0
  78. data/lib/views/backend/spree/admin/payments/source_views/_stripe_elements.html.erb +1 -0
  79. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +86 -0
  80. data/lib/views/frontend/spree/checkout/payment/_stripe_ach.html.erb +81 -0
  81. data/lib/views/frontend/spree/checkout/payment/_stripe_ach_verify.html.erb +16 -0
  82. data/lib/views/frontend/spree/checkout/payment/_stripe_additional_info.html.erb +16 -0
  83. data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +109 -0
  84. data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +110 -0
  85. data/script/rails +7 -0
  86. data/spec/factories/check_factory.rb +10 -0
  87. data/spec/features/admin/stripe_elements_payment_spec.rb +100 -0
  88. data/spec/features/stripe_checkout_spec.rb +130 -0
  89. data/spec/features/stripe_elements_3ds_checkout_spec.rb +225 -0
  90. data/spec/models/gateway/authorize_net_cim_spec.rb +29 -0
  91. data/spec/models/gateway/authorize_net_spec.rb +23 -0
  92. data/spec/models/gateway/balanced_gateway_spec.rb +17 -0
  93. data/spec/models/gateway/banwire_spec.rb +11 -0
  94. data/spec/models/gateway/beanstream_spec.rb +17 -0
  95. data/spec/models/gateway/braintree_gateway_spec.rb +420 -0
  96. data/spec/models/gateway/card_save_spec.rb +11 -0
  97. data/spec/models/gateway/cyber_source_spec.rb +11 -0
  98. data/spec/models/gateway/data_cash_spec.rb +11 -0
  99. data/spec/models/gateway/epay_spec.rb +11 -0
  100. data/spec/models/gateway/eway_rapid_spec.rb +23 -0
  101. data/spec/models/gateway/eway_spec.rb +29 -0
  102. data/spec/models/gateway/maxipago_spec.rb +17 -0
  103. data/spec/models/gateway/moneris_spec.rb +11 -0
  104. data/spec/models/gateway/pay_junction_spec.rb +23 -0
  105. data/spec/models/gateway/pay_pal_spec.rb +11 -0
  106. data/spec/models/gateway/payflow_pro_spec.rb +23 -0
  107. data/spec/models/gateway/paymill_spec.rb +11 -0
  108. data/spec/models/gateway/pin_gateway_spec.rb +54 -0
  109. data/spec/models/gateway/quickpay_spec.rb +11 -0
  110. data/spec/models/gateway/sage_pay_spec.rb +11 -0
  111. data/spec/models/gateway/secure_pay_au_spec.rb +11 -0
  112. data/spec/models/gateway/stripe_ach_gateway_spec.rb +186 -0
  113. data/spec/models/gateway/stripe_gateway_spec.rb +199 -0
  114. data/spec/models/gateway/usa_epay_transaction_spec.rb +49 -0
  115. data/spec/models/gateway/worldpay_spec.rb +11 -0
  116. data/spec/models/spree/order_spec.rb +79 -0
  117. data/spec/requests/apple_pay_domain_verification.rb +45 -0
  118. data/spec/requests/spree/api/v2/storefront/intents_controller_spec.rb +198 -0
  119. data/spec/spec_helper.rb +10 -0
  120. data/spec/support/order_walktrough.rb +73 -0
  121. data/spec/support/wait_for_stripe.rb +27 -0
  122. data/spec/support/within_stripe_3ds_popup.rb +10 -0
  123. data/spree_gateway.gemspec +37 -0
  124. 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,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
4
+ ENGINE_PATH = File.expand_path('../../lib/spree_gateway/engine', __FILE__)
5
+
6
+ require 'rails/all'
7
+ require 'rails/engine/commands'
@@ -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