solidus_stripe 1.0.0 → 1.1.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/.gem_release.yml +2 -5
- data/.github/stale.yml +17 -0
- data/.gitignore +11 -4
- data/.rspec +2 -1
- data/.rubocop.yml +2 -319
- data/Gemfile +16 -9
- data/LICENSE +26 -0
- data/README.md +58 -1
- data/Rakefile +3 -20
- data/app/assets/javascripts/solidus_stripe/stripe-init.js +1 -0
- data/app/assets/javascripts/solidus_stripe/stripe-init/base.js +180 -0
- data/app/controllers/solidus_stripe/intents_controller.rb +52 -0
- data/app/controllers/solidus_stripe/payment_request_controller.rb +42 -0
- data/app/controllers/spree/stripe_controller.rb +13 -0
- data/app/models/solidus_stripe/address_from_params_service.rb +57 -0
- data/app/models/solidus_stripe/prepare_order_for_payment_service.rb +46 -0
- data/app/models/solidus_stripe/shipping_rates_service.rb +46 -0
- data/app/models/spree/payment_method/stripe_credit_card.rb +57 -8
- data/bin/console +17 -0
- data/bin/rails +12 -4
- data/bin/setup +8 -0
- data/config/routes.rb +11 -0
- data/lib/assets/stylesheets/spree/frontend/solidus_stripe.scss +5 -0
- data/lib/generators/solidus_stripe/install/install_generator.rb +3 -13
- data/lib/solidus_stripe/engine.rb +14 -2
- data/lib/solidus_stripe/version.rb +1 -1
- data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +2 -0
- data/lib/views/frontend/spree/checkout/payment/v2/_javascript.html.erb +13 -12
- data/lib/views/frontend/spree/checkout/payment/v3/_elements_js.html.erb +28 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +42 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_intents.html.erb +5 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_intents_js.html.erb +48 -0
- data/lib/views/frontend/spree/checkout/payment/v3/_stripe.html.erb +3 -131
- data/lib/views/frontend/spree/orders/_stripe_payment_request_button.html.erb +92 -0
- data/solidus_stripe.gemspec +15 -20
- data/spec/features/stripe_checkout_spec.rb +196 -35
- data/spec/models/solidus_stripe/address_from_params_service_spec.rb +62 -0
- data/spec/models/solidus_stripe/prepare_order_for_payment_service_spec.rb +65 -0
- data/spec/models/solidus_stripe/shipping_rates_service_spec.rb +54 -0
- data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +44 -5
- data/spec/spec_helper.rb +9 -7
- metadata +38 -136
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            <script>
         | 
| 2 | 
            +
              // Stripe Intents JS code
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              var cardNumber = initElements();
         | 
| 5 | 
            +
              var paymentRequest = setUpPaymentRequest(SolidusStripe.paymentMethod.config.payment_request);
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              form.bind('submit', function(event) {
         | 
| 8 | 
            +
                if (element.is(':visible')) {
         | 
| 9 | 
            +
                  event.preventDefault();
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  errorElement.text('').hide();
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  stripe.createPaymentMethod(
         | 
| 14 | 
            +
                    'card',
         | 
| 15 | 
            +
                    cardNumber
         | 
| 16 | 
            +
                  ).then(function(result) {
         | 
| 17 | 
            +
                    handlePayment(result);
         | 
| 18 | 
            +
                  });
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
              });
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              function handlePayment(payment) {
         | 
| 23 | 
            +
                if (payment.error) {
         | 
| 24 | 
            +
                  showError(payment.error.message);
         | 
| 25 | 
            +
                } else {
         | 
| 26 | 
            +
                  stripeTokenHandler(payment.paymentMethod);
         | 
| 27 | 
            +
                  fetch('/stripe/confirm_intents', {
         | 
| 28 | 
            +
                    method: 'POST',
         | 
| 29 | 
            +
                    headers: {
         | 
| 30 | 
            +
                      'Content-Type': 'application/json'
         | 
| 31 | 
            +
                    },
         | 
| 32 | 
            +
                    body: JSON.stringify({
         | 
| 33 | 
            +
                      spree_payment_method_id: SolidusStripe.paymentMethod.config.id,
         | 
| 34 | 
            +
                      stripe_payment_method_id: payment.paymentMethod.id,
         | 
| 35 | 
            +
                      authenticity_token: authToken
         | 
| 36 | 
            +
                    })
         | 
| 37 | 
            +
                  }).then(function(response) {
         | 
| 38 | 
            +
                    response.json().then(function(json) {
         | 
| 39 | 
            +
                      handleServerResponse(json, payment);
         | 
| 40 | 
            +
                    })
         | 
| 41 | 
            +
                  });
         | 
| 42 | 
            +
                }
         | 
| 43 | 
            +
              };
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              function submitPayment(_payment) {
         | 
| 46 | 
            +
                form.unbind('submit').submit();
         | 
| 47 | 
            +
              }
         | 
| 48 | 
            +
            </script>
         | 
| @@ -1,133 +1,5 @@ | |
| 1 | 
            -
            <%=  | 
| 2 | 
            -
            <% param_prefix = "payment_source[#{payment_method.id}]" %>
         | 
| 1 | 
            +
            <%= render 'spree/checkout/payment/v3/form_elements', payment_method: payment_method %>
         | 
| 3 2 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
              <%= label_tag "name_on_card_#{payment_method.id}", t('spree.name_on_card') %>
         | 
| 6 | 
            -
              <%= text_field_tag "#{param_prefix}[name]", "#{@order.billing_firstname} #{@order.billing_lastname}", { id: "name_on_card_#{payment_method.id}", autocomplete: "cc-name" } %>
         | 
| 7 | 
            -
            </div>
         | 
| 3 | 
            +
            <%= javascript_include_tag "solidus_stripe/stripe-init.js" %>
         | 
| 8 4 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
              <%= label_tag "card_number", t('spree.card_number') %>
         | 
| 11 | 
            -
              <div id="card_number"></div>
         | 
| 12 | 
            -
              <span id="card_type" style="display:none;">
         | 
| 13 | 
            -
                ( <span id="looks_like" ><%= t('spree.card_type_is') %> <span id="type"></span></span>
         | 
| 14 | 
            -
                  <span id="unrecognized"><%= t('spree.unrecognized_card_type') %></span>
         | 
| 15 | 
            -
                )
         | 
| 16 | 
            -
              </span>
         | 
| 17 | 
            -
            </div>
         | 
| 18 | 
            -
             | 
| 19 | 
            -
            <div class="field field-required" data-hook="card_expiration">
         | 
| 20 | 
            -
              <%= label_tag "card_expiry", t('spree.expiration') %>
         | 
| 21 | 
            -
              <div id="card_expiry"></div>
         | 
| 22 | 
            -
            </div>
         | 
| 23 | 
            -
             | 
| 24 | 
            -
            <div class="field field-required" data-hook="card_code">
         | 
| 25 | 
            -
              <%= label_tag "card_cvc", t('spree.card_code') %>
         | 
| 26 | 
            -
              <div id="card_cvc"></div>
         | 
| 27 | 
            -
              <%= link_to "(#{t('spree.what_is_this')})", spree.cvv_path, target: '_blank', "data-hook" => "cvv_link", id: "cvv_link" %>
         | 
| 28 | 
            -
            </div>
         | 
| 29 | 
            -
             | 
| 30 | 
            -
            <div id="card-errors" class='errorExplanation' role="alert" style="display: none"></div>
         | 
| 31 | 
            -
             | 
| 32 | 
            -
            <% if @order.bill_address %>
         | 
| 33 | 
            -
              <%= fields_for "#{param_prefix}[address_attributes]", @order.bill_address do |f| %>
         | 
| 34 | 
            -
                <%= render partial: 'spree/address/form_hidden', locals: { form: f } %>
         | 
| 35 | 
            -
              <% end %>
         | 
| 36 | 
            -
            <% end %>
         | 
| 37 | 
            -
             | 
| 38 | 
            -
            <%= hidden_field_tag "#{param_prefix}[cc_type]", '', id: "cc_type", class: 'ccType' %>
         | 
| 39 | 
            -
             | 
| 40 | 
            -
            <script src="https://js.stripe.com/v3/"></script>
         | 
| 41 | 
            -
             | 
| 42 | 
            -
            <script>
         | 
| 43 | 
            -
              Spree.stripePaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
         | 
| 44 | 
            -
             | 
| 45 | 
            -
              var stripe = Stripe("<%= payment_method.preferred_publishable_key %>");
         | 
| 46 | 
            -
             | 
| 47 | 
            -
              var elements = stripe.elements({locale: 'en'});
         | 
| 48 | 
            -
             | 
| 49 | 
            -
              var style = {
         | 
| 50 | 
            -
                base: {
         | 
| 51 | 
            -
                  color: 'black',
         | 
| 52 | 
            -
                  fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
         | 
| 53 | 
            -
                  fontSmoothing: 'antialiased',
         | 
| 54 | 
            -
                  fontSize: '14px',
         | 
| 55 | 
            -
                  '::placeholder': {
         | 
| 56 | 
            -
                    color: 'silver'
         | 
| 57 | 
            -
                  }
         | 
| 58 | 
            -
                },
         | 
| 59 | 
            -
                invalid: {
         | 
| 60 | 
            -
                  color: 'red',
         | 
| 61 | 
            -
                  iconColor: 'red'
         | 
| 62 | 
            -
                }
         | 
| 63 | 
            -
              };
         | 
| 64 | 
            -
             | 
| 65 | 
            -
              var cardNumber = elements.create('cardNumber', {style: style});
         | 
| 66 | 
            -
              var cardExpiry = elements.create('cardExpiry', {style: style});
         | 
| 67 | 
            -
              var cardCvc = elements.create('cardCvc', {style: style});
         | 
| 68 | 
            -
             | 
| 69 | 
            -
              cardNumber.mount('#card_number');
         | 
| 70 | 
            -
              cardExpiry.mount('#card_expiry');
         | 
| 71 | 
            -
              cardCvc.mount('#card_cvc');
         | 
| 72 | 
            -
             | 
| 73 | 
            -
              $(function() {
         | 
| 74 | 
            -
                var form = Spree.stripePaymentMethod.parents('form');
         | 
| 75 | 
            -
                var submitButton = form.find('input[type="submit"]');
         | 
| 76 | 
            -
                var errorElement = form.find('#card-errors');
         | 
| 77 | 
            -
                var cardType = form.find('input#cc_type');
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                cardNumber.addEventListener('change', function(event) {
         | 
| 80 | 
            -
                  if (event.error) {
         | 
| 81 | 
            -
                    errorElement.text(event.error.message).show();
         | 
| 82 | 
            -
                  } else {
         | 
| 83 | 
            -
                    errorElement.hide().text('');
         | 
| 84 | 
            -
                  }
         | 
| 85 | 
            -
                });
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                form.bind('submit', function(event) {
         | 
| 88 | 
            -
                  if (Spree.stripePaymentMethod.is(':visible')) {
         | 
| 89 | 
            -
                    event.preventDefault();
         | 
| 90 | 
            -
             | 
| 91 | 
            -
                    stripe.createToken(cardNumber).then(function(result) {
         | 
| 92 | 
            -
                      if (result.error) {
         | 
| 93 | 
            -
                        errorElement.text(result.error.message).show();
         | 
| 94 | 
            -
                        setTimeout(function() {
         | 
| 95 | 
            -
                          $.rails.enableElement(submitButton[0]);
         | 
| 96 | 
            -
                          submitButton.removeAttr('disabled').removeClass('disabled');
         | 
| 97 | 
            -
                        }, 100);
         | 
| 98 | 
            -
                      } else {
         | 
| 99 | 
            -
                        stripeTokenHandler(result.token);
         | 
| 100 | 
            -
                      }
         | 
| 101 | 
            -
                    });
         | 
| 102 | 
            -
                  }
         | 
| 103 | 
            -
                });
         | 
| 104 | 
            -
             | 
| 105 | 
            -
                function stripeTokenHandler(token) {
         | 
| 106 | 
            -
                  var paymentMethodId = Spree.stripePaymentMethod.prop('id').split("_")[2];
         | 
| 107 | 
            -
                  var baseSelector = `<input type='hidden' class='stripeToken' name='payment_source[${paymentMethodId}]`;
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                  Spree.stripePaymentMethod.append(`${baseSelector}[gateway_payment_profile_id]' value='${token.id}'/>`);
         | 
| 110 | 
            -
                  Spree.stripePaymentMethod.append(`${baseSelector}[last_digits]' value='${token.card.last4}'/>`);
         | 
| 111 | 
            -
                  Spree.stripePaymentMethod.append(`${baseSelector}[month]' value='${token.card.exp_month}'/>`);
         | 
| 112 | 
            -
                  Spree.stripePaymentMethod.append(`${baseSelector}[year]' value='${token.card.exp_year}'/>`);
         | 
| 113 | 
            -
                  cardType.val(mapCC(token.card.type));
         | 
| 114 | 
            -
                  form[0].submit();
         | 
| 115 | 
            -
                };
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                function mapCC(ccType) {
         | 
| 118 | 
            -
                  if (ccType === 'MasterCard') {
         | 
| 119 | 
            -
                    return 'mastercard';
         | 
| 120 | 
            -
                  } else if (ccType === 'Visa') {
         | 
| 121 | 
            -
                    return 'visa';
         | 
| 122 | 
            -
                  } else if (ccType === 'American Express') {
         | 
| 123 | 
            -
                    return 'amex';
         | 
| 124 | 
            -
                  } else if (ccType === 'Discover') {
         | 
| 125 | 
            -
                    return 'discover';
         | 
| 126 | 
            -
                  } else if (ccType === 'Diners Club') {
         | 
| 127 | 
            -
                    return 'dinersclub';
         | 
| 128 | 
            -
                  } else if (ccType === 'JCB') {
         | 
| 129 | 
            -
                    return 'jcb';
         | 
| 130 | 
            -
                  }
         | 
| 131 | 
            -
                };
         | 
| 132 | 
            -
              })
         | 
| 133 | 
            -
            </script>
         | 
| 5 | 
            +
            <%= render "spree/checkout/payment/v3/elements_js" %>
         | 
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            <% if current_order.present? && cart_checkout_payment_method.present? %>
         | 
| 2 | 
            +
              <div id="stripe-payment-request" class="stripe-payment-request" style="display:none">
         | 
| 3 | 
            +
                <div id="payment-request-button"
         | 
| 4 | 
            +
                  data-stripe-config="<%= cart_checkout_payment_method.stripe_config(current_order).to_json %>"
         | 
| 5 | 
            +
                  data-order-token="<%= current_order.guest_token %>"
         | 
| 6 | 
            +
                  data-submit-url="<%= api_checkout_path(current_order.number) %>"
         | 
| 7 | 
            +
                  data-complete-url="<%= checkout_path %>"
         | 
| 8 | 
            +
                  class="payment-request-button">
         | 
| 9 | 
            +
                </div>
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                <div id="card-errors" class='errorExplanation' role="alert" style="display: none">
         | 
| 12 | 
            +
                </div>
         | 
| 13 | 
            +
              </div>
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              <script src="https://js.stripe.com/v3/"></script>
         | 
| 16 | 
            +
              <%= javascript_include_tag "solidus_stripe/stripe-init" %>
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              <script>
         | 
| 19 | 
            +
                var paymentRequestConfig = SolidusStripe.paymentMethod.config.payment_request;
         | 
| 20 | 
            +
                var errorElement = $('#card-errors');
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                if (typeof paymentRequestConfig !== 'undefined') {
         | 
| 23 | 
            +
                  paymentRequestConfig.requestShipping = true;
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  var onPrButtonMounted = function(buttonId, success) {
         | 
| 26 | 
            +
                    var container = document.getElementById('stripe-payment-request');
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    if (success) {
         | 
| 29 | 
            +
                      container.style.display = '';
         | 
| 30 | 
            +
                    } else {
         | 
| 31 | 
            +
                      container.style.display = 'none';
         | 
| 32 | 
            +
                    }
         | 
| 33 | 
            +
                  };
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  var paymentRequest = setUpPaymentRequest(paymentRequestConfig, onPrButtonMounted);
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  function handlePayment(result) {
         | 
| 38 | 
            +
                    fetch('/stripe/update_order', {
         | 
| 39 | 
            +
                      method: 'POST',
         | 
| 40 | 
            +
                      headers: { 'Content-Type': 'application/json' },
         | 
| 41 | 
            +
                      body: JSON.stringify({
         | 
| 42 | 
            +
                        shipping_address: result.shippingAddress,
         | 
| 43 | 
            +
                        shipping_option: result.shippingOption,
         | 
| 44 | 
            +
                        email: result.payerEmail,
         | 
| 45 | 
            +
                        name: result.payerName,
         | 
| 46 | 
            +
                        authenticity_token: authToken
         | 
| 47 | 
            +
                      })
         | 
| 48 | 
            +
                    }).then(function(response) {
         | 
| 49 | 
            +
                      response.json().then(function(json) {
         | 
| 50 | 
            +
                        handleServerResponse(json, result);
         | 
| 51 | 
            +
                      })
         | 
| 52 | 
            +
                    });
         | 
| 53 | 
            +
                  };
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  function stripeTokenHandler(token) {
         | 
| 56 | 
            +
                    return {
         | 
| 57 | 
            +
                      order: {
         | 
| 58 | 
            +
                        payments_attributes: [
         | 
| 59 | 
            +
                          {
         | 
| 60 | 
            +
                            payment_method_id: SolidusStripe.paymentMethod.config.id,
         | 
| 61 | 
            +
                            source_attributes: {
         | 
| 62 | 
            +
                              gateway_payment_profile_id: token.id,
         | 
| 63 | 
            +
                              last_digits: token.card.last4,
         | 
| 64 | 
            +
                              month: token.card.exp_month,
         | 
| 65 | 
            +
                              year: token.card.exp_year
         | 
| 66 | 
            +
                            }
         | 
| 67 | 
            +
                          }
         | 
| 68 | 
            +
                        ]
         | 
| 69 | 
            +
                      }
         | 
| 70 | 
            +
                    }
         | 
| 71 | 
            +
                  };
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  function submitPayment(payment) {
         | 
| 74 | 
            +
                    $.ajax({
         | 
| 75 | 
            +
                      url : $('[data-submit-url]').data('submit-url'),
         | 
| 76 | 
            +
                      headers: {
         | 
| 77 | 
            +
                        'X-Spree-Order-Token': $('[data-order-token]').data('order-token')
         | 
| 78 | 
            +
                      },
         | 
| 79 | 
            +
                      type : 'PATCH',
         | 
| 80 | 
            +
                      contentType: 'application/json',
         | 
| 81 | 
            +
                      data : JSON.stringify(stripeTokenHandler(payment.paymentMethod)),
         | 
| 82 | 
            +
                      success: function() {
         | 
| 83 | 
            +
                        window.location = $('[data-complete-url]').data('complete-url');
         | 
| 84 | 
            +
                      },
         | 
| 85 | 
            +
                      error: function(xhr,status,error) {
         | 
| 86 | 
            +
                        showError(xhr.responseJSON.error);
         | 
| 87 | 
            +
                      }
         | 
| 88 | 
            +
                    });
         | 
| 89 | 
            +
                  };
         | 
| 90 | 
            +
                }
         | 
| 91 | 
            +
              </script>
         | 
| 92 | 
            +
            <% end %>
         | 
    
        data/solidus_stripe.gemspec
    CHANGED
    
    | @@ -1,15 +1,11 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 3 | 
            +
            $:.push File.expand_path('lib', __dir__)
         | 
| 5 4 | 
             
            require 'solidus_stripe/version'
         | 
| 6 5 |  | 
| 7 | 
            -
            # encoding: UTF-8
         | 
| 8 | 
            -
             | 
| 9 6 | 
             
            Gem::Specification.new do |s|
         | 
| 10 | 
            -
              s. | 
| 11 | 
            -
              s. | 
| 12 | 
            -
              s.version     = SolidusStripe::VERSION
         | 
| 7 | 
            +
              s.name = 'solidus_stripe'
         | 
| 8 | 
            +
              s.version = SolidusStripe::VERSION
         | 
| 13 9 | 
             
              s.summary     = "Stripe Payment Method for Solidus"
         | 
| 14 10 | 
             
              s.description = s.summary
         | 
| 15 11 | 
             
              s.required_ruby_version = ">= 2.2"
         | 
| @@ -19,27 +15,26 @@ Gem::Specification.new do |s| | |
| 19 15 | 
             
              s.homepage     = "https://solidus.io"
         | 
| 20 16 | 
             
              s.license      = 'BSD-3'
         | 
| 21 17 |  | 
| 18 | 
            +
              if s.respond_to?(:metadata)
         | 
| 19 | 
            +
                s.metadata["homepage_uri"] = s.homepage if s.homepage
         | 
| 20 | 
            +
                s.metadata["source_code_uri"] = s.homepage if s.homepage
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 22 23 | 
             
              s.files        = `git ls-files`.split("\n")
         | 
| 23 24 | 
             
              s.test_files   = `git ls-files -- spec/*`.split("\n")
         | 
| 24 25 | 
             
              s.require_path = "lib"
         | 
| 25 26 | 
             
              s.requirements << "none"
         | 
| 26 27 |  | 
| 27 | 
            -
              s. | 
| 28 | 
            -
              s. | 
| 28 | 
            +
              s.bindir = "exe"
         | 
| 29 | 
            +
              s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 29 30 |  | 
| 31 | 
            +
              s.add_dependency 'solidus_core', ['>= 2.3', '< 3']
         | 
| 32 | 
            +
              s.add_dependency 'solidus_support', '~> 0.4.0'
         | 
| 30 33 | 
             
              # ActiveMerchant v1.58 through v1.59 introduced a breaking change
         | 
| 31 34 | 
             
              # to the stripe gateway.
         | 
| 32 35 | 
             
              #
         | 
| 33 36 | 
             
              # This was resolved in v1.60, but we still need to skip 1.58 & 1.59.
         | 
| 34 | 
            -
              s.add_dependency "activemerchant", " | 
| 35 | 
            -
             | 
| 36 | 
            -
              s.add_development_dependency  | 
| 37 | 
            -
              s.add_development_dependency "capybara-screenshot"
         | 
| 38 | 
            -
              s.add_development_dependency "database_cleaner", "~> 1.5"
         | 
| 39 | 
            -
              s.add_development_dependency "factory_bot", "~> 4.4"
         | 
| 40 | 
            -
              s.add_development_dependency "gem-release", "~> 2.0"
         | 
| 41 | 
            -
              s.add_development_dependency "rspec-rails", "~> 3.2"
         | 
| 42 | 
            -
              s.add_development_dependency 'selenium-webdriver', '~> 3.142'
         | 
| 43 | 
            -
              s.add_development_dependency "simplecov"
         | 
| 44 | 
            -
              s.add_development_dependency "sqlite3"
         | 
| 37 | 
            +
              s.add_dependency "activemerchant", ">= 1.100" # includes "Stripe Payment Intents: Fix fallback for Store"
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              s.add_development_dependency 'solidus_dev_support'
         | 
| 45 40 | 
             
            end
         | 
| @@ -15,7 +15,8 @@ RSpec.describe "Stripe checkout", type: :feature do | |
| 15 15 | 
             
                  name: "Stripe",
         | 
| 16 16 | 
             
                  preferred_secret_key: "sk_test_VCZnDv3GLU15TRvn8i2EsaAN",
         | 
| 17 17 | 
             
                  preferred_publishable_key: "pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg",
         | 
| 18 | 
            -
                  preferred_v3_elements: preferred_v3_elements
         | 
| 18 | 
            +
                  preferred_v3_elements: preferred_v3_elements,
         | 
| 19 | 
            +
                  preferred_v3_intents: preferred_v3_intents
         | 
| 19 20 | 
             
                )
         | 
| 20 21 |  | 
| 21 22 | 
             
                FactoryBot.create(:product, name: "DL-44")
         | 
| @@ -61,6 +62,7 @@ RSpec.describe "Stripe checkout", type: :feature do | |
| 61 62 |  | 
| 62 63 | 
             
              context 'when using Stripe V2 API library' do
         | 
| 63 64 | 
             
                let(:preferred_v3_elements) { false }
         | 
| 65 | 
            +
                let(:preferred_v3_intents) { false }
         | 
| 64 66 |  | 
| 65 67 | 
             
                before do
         | 
| 66 68 | 
             
                  click_on "Save and Continue"
         | 
| @@ -161,32 +163,91 @@ RSpec.describe "Stripe checkout", type: :feature do | |
| 161 163 | 
             
                end
         | 
| 162 164 | 
             
              end
         | 
| 163 165 |  | 
| 164 | 
            -
               | 
| 166 | 
            +
              shared_examples "Stripe Elements invalid payments" do
         | 
| 167 | 
            +
                it "shows an error with a missing credit card number" do
         | 
| 168 | 
            +
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 169 | 
            +
                  within_frame(find '#card_expiry iframe') do
         | 
| 170 | 
            +
                    '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
                  click_button "Save and Continue"
         | 
| 173 | 
            +
                  expect(page).to have_content("Your card number is incomplete.")
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                it "shows an error with a missing expiration date" do
         | 
| 177 | 
            +
                  within_frame find('#card_number iframe') do
         | 
| 178 | 
            +
                    '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 181 | 
            +
                  click_button "Save and Continue"
         | 
| 182 | 
            +
                  expect(page).to have_content("Your card's expiration date is incomplete.")
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                it "shows an error with an invalid credit card number" do
         | 
| 186 | 
            +
                  within_frame find('#card_number iframe') do
         | 
| 187 | 
            +
                    '1111 1111 1111 1111'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 190 | 
            +
                  within_frame(find '#card_expiry iframe') do
         | 
| 191 | 
            +
                    '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
                  click_button "Save and Continue"
         | 
| 194 | 
            +
                  expect(page).to have_content("Your card number is invalid.")
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                it "shows an error with invalid security fields" do
         | 
| 198 | 
            +
                  within_frame find('#card_number iframe') do
         | 
| 199 | 
            +
                    '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '12' }
         | 
| 202 | 
            +
                  within_frame(find '#card_expiry iframe') do
         | 
| 203 | 
            +
                    '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                  click_button "Save and Continue"
         | 
| 206 | 
            +
                  expect(page).to have_content("Your card's security code is incomplete.")
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                it "shows an error with invalid expiry fields" do
         | 
| 210 | 
            +
                  within_frame find('#card_number iframe') do
         | 
| 211 | 
            +
                    '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 214 | 
            +
                  within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "013" }
         | 
| 215 | 
            +
                  click_button "Save and Continue"
         | 
| 216 | 
            +
                  expect(page).to have_content("Your card's expiration date is incomplete.")
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
              end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
              context 'when using Stripe V3 API libarary with Elements', :js do
         | 
| 165 221 | 
             
                let(:preferred_v3_elements) { true }
         | 
| 222 | 
            +
                let(:preferred_v3_intents) { false }
         | 
| 166 223 |  | 
| 167 224 | 
             
                before do
         | 
| 168 225 | 
             
                  click_on "Save and Continue"
         | 
| 169 226 | 
             
                  expect(page).to have_current_path("/checkout/payment")
         | 
| 170 227 | 
             
                end
         | 
| 171 228 |  | 
| 172 | 
            -
                it "can process a valid payment" | 
| 229 | 
            +
                it "can process a valid payment" do
         | 
| 173 230 | 
             
                  within_frame find('#card_number iframe') do
         | 
| 174 231 | 
             
                    '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 175 232 | 
             
                  end
         | 
| 176 233 | 
             
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 177 | 
            -
                  within_frame(find '#card_expiry iframe')  | 
| 234 | 
            +
                  within_frame(find '#card_expiry iframe') do
         | 
| 235 | 
            +
                    '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 236 | 
            +
                  end
         | 
| 178 237 | 
             
                  click_button "Save and Continue"
         | 
| 179 238 | 
             
                  expect(page).to have_current_path("/checkout/confirm")
         | 
| 180 239 | 
             
                  click_button "Place Order"
         | 
| 181 240 | 
             
                  expect(page).to have_content("Your order has been processed successfully")
         | 
| 182 241 | 
             
                end
         | 
| 183 242 |  | 
| 184 | 
            -
                it "can re-use saved cards" | 
| 243 | 
            +
                it "can re-use saved cards" do
         | 
| 185 244 | 
             
                  within_frame find('#card_number iframe') do
         | 
| 186 245 | 
             
                    '4242 4242 4242 4242'.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 187 246 | 
             
                  end
         | 
| 188 247 | 
             
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 189 | 
            -
                  within_frame(find '#card_expiry iframe')  | 
| 248 | 
            +
                  within_frame(find '#card_expiry iframe') do
         | 
| 249 | 
            +
                    '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 250 | 
            +
                  end
         | 
| 190 251 | 
             
                  click_button "Save and Continue"
         | 
| 191 252 | 
             
                  expect(page).to have_current_path("/checkout/confirm")
         | 
| 192 253 | 
             
                  click_button "Place Order"
         | 
| @@ -230,50 +291,150 @@ RSpec.describe "Stripe checkout", type: :feature do | |
| 230 291 | 
             
                  expect(page).to have_content("Your order has been processed successfully")
         | 
| 231 292 | 
             
                end
         | 
| 232 293 |  | 
| 233 | 
            -
                 | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 294 | 
            +
                it_behaves_like "Stripe Elements invalid payments"
         | 
| 295 | 
            +
              end
         | 
| 296 | 
            +
             | 
| 297 | 
            +
              context "when using Stripe V3 API libarary with Intents", :js do
         | 
| 298 | 
            +
                let(:preferred_v3_elements) { false }
         | 
| 299 | 
            +
                let(:preferred_v3_intents) { true }
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                before do
         | 
| 302 | 
            +
                  click_on "Save and Continue"
         | 
| 303 | 
            +
                  expect(page).to have_current_path("/checkout/payment")
         | 
| 238 304 | 
             
                end
         | 
| 239 305 |  | 
| 240 | 
            -
                 | 
| 241 | 
            -
                   | 
| 242 | 
            -
             | 
| 306 | 
            +
                context "when using a valid 3D Secure card" do
         | 
| 307 | 
            +
                  let(:card_number) { "4000 0027 6000 3184" }
         | 
| 308 | 
            +
             | 
| 309 | 
            +
                  it "successfully completes the checkout" do
         | 
| 310 | 
            +
                    within_frame find('#card_number iframe') do
         | 
| 311 | 
            +
                      card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 312 | 
            +
                    end
         | 
| 313 | 
            +
                    within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 314 | 
            +
                    within_frame(find '#card_expiry iframe') do
         | 
| 315 | 
            +
                      '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 316 | 
            +
                    end
         | 
| 317 | 
            +
             | 
| 318 | 
            +
                    click_button "Save and Continue"
         | 
| 319 | 
            +
             | 
| 320 | 
            +
                    within_3d_secure_modal do
         | 
| 321 | 
            +
                      expect(page).to have_content '$19.99 using 3D Secure'
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                      click_button 'Complete authentication'
         | 
| 324 | 
            +
                    end
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                    expect(page).to have_current_path("/checkout/confirm")
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                    click_button "Place Order"
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                    expect(page).to have_content("Your order has been processed successfully")
         | 
| 243 331 | 
             
                  end
         | 
| 244 | 
            -
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 245 | 
            -
                  click_button "Save and Continue"
         | 
| 246 | 
            -
                  expect(page).to have_content("Your card's expiration date is incomplete.")
         | 
| 247 332 | 
             
                end
         | 
| 248 333 |  | 
| 249 | 
            -
                 | 
| 250 | 
            -
                   | 
| 251 | 
            -
             | 
| 334 | 
            +
                context "when using a card without enough money" do
         | 
| 335 | 
            +
                  let(:card_number) { "4000 0000 0000 9995" }
         | 
| 336 | 
            +
             | 
| 337 | 
            +
                  it "fails the payment" do
         | 
| 338 | 
            +
                    within_frame find('#card_number iframe') do
         | 
| 339 | 
            +
                      card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 340 | 
            +
                    end
         | 
| 341 | 
            +
                    within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 342 | 
            +
                    within_frame(find '#card_expiry iframe') do
         | 
| 343 | 
            +
                      '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 344 | 
            +
                    end
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                    click_button "Save and Continue"
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                    expect(page).to have_content "Your card has insufficient funds."
         | 
| 252 349 | 
             
                  end
         | 
| 253 | 
            -
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 254 | 
            -
                  within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
         | 
| 255 | 
            -
                  click_button "Save and Continue"
         | 
| 256 | 
            -
                  expect(page).to have_content("Your card number is invalid.")
         | 
| 257 350 | 
             
                end
         | 
| 258 351 |  | 
| 259 | 
            -
                 | 
| 260 | 
            -
                   | 
| 261 | 
            -
             | 
| 352 | 
            +
                context "when entering the wrong 3D verification code" do
         | 
| 353 | 
            +
                  let(:card_number) { "4000 0084 0000 1629" }
         | 
| 354 | 
            +
             | 
| 355 | 
            +
                  it "fails the payment" do
         | 
| 356 | 
            +
                    within_frame find('#card_number iframe') do
         | 
| 357 | 
            +
                      card_number.split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 358 | 
            +
                    end
         | 
| 359 | 
            +
                    within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 360 | 
            +
                    within_frame(find '#card_expiry iframe') do
         | 
| 361 | 
            +
                      '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 362 | 
            +
                    end
         | 
| 363 | 
            +
             | 
| 364 | 
            +
                    click_button "Save and Continue"
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                    within_3d_secure_modal do
         | 
| 367 | 
            +
                      click_button 'Complete authentication'
         | 
| 368 | 
            +
                    end
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                    expect(page).to have_content "Your card was declined."
         | 
| 262 371 | 
             
                  end
         | 
| 263 | 
            -
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '12' }
         | 
| 264 | 
            -
                  within_frame(find '#card_expiry iframe') { fill_in 'exp-date', with: "0132" }
         | 
| 265 | 
            -
                  click_button "Save and Continue"
         | 
| 266 | 
            -
                  expect(page).to have_content("Your card's security code is incomplete.")
         | 
| 267 372 | 
             
                end
         | 
| 268 373 |  | 
| 269 | 
            -
                it " | 
| 374 | 
            +
                it "can re-use saved cards" do
         | 
| 270 375 | 
             
                  within_frame find('#card_number iframe') do
         | 
| 271 | 
            -
                     | 
| 376 | 
            +
                    "4000 0027 6000 3184".split('').each { |n| find_field('cardnumber').native.send_keys(n) }
         | 
| 272 377 | 
             
                  end
         | 
| 273 378 | 
             
                  within_frame(find '#card_cvc iframe') { fill_in 'cvc', with: '123' }
         | 
| 274 | 
            -
                  within_frame(find '#card_expiry iframe')  | 
| 379 | 
            +
                  within_frame(find '#card_expiry iframe') do
         | 
| 380 | 
            +
                    '0132'.split('').each { |n| find_field('exp-date').native.send_keys(n) }
         | 
| 381 | 
            +
                  end
         | 
| 275 382 | 
             
                  click_button "Save and Continue"
         | 
| 276 | 
            -
             | 
| 383 | 
            +
             | 
| 384 | 
            +
                  within_3d_secure_modal do
         | 
| 385 | 
            +
                    click_button 'Complete authentication'
         | 
| 386 | 
            +
                  end
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                  expect(page).to have_current_path("/checkout/confirm")
         | 
| 389 | 
            +
                  click_button "Place Order"
         | 
| 390 | 
            +
                  expect(page).to have_content("Your order has been processed successfully")
         | 
| 391 | 
            +
             | 
| 392 | 
            +
                  visit spree.root_path
         | 
| 393 | 
            +
                  click_link "DL-44"
         | 
| 394 | 
            +
                  click_button "Add To Cart"
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                  expect(page).to have_current_path("/cart")
         | 
| 397 | 
            +
                  click_button "Checkout"
         | 
| 398 | 
            +
             | 
| 399 | 
            +
                  # Address
         | 
| 400 | 
            +
                  expect(page).to have_current_path("/checkout/address")
         | 
| 401 | 
            +
             | 
| 402 | 
            +
                  within("#billing") do
         | 
| 403 | 
            +
                    fill_in "First Name", with: "Han"
         | 
| 404 | 
            +
                    fill_in "Last Name", with: "Solo"
         | 
| 405 | 
            +
                    fill_in "Street Address", with: "YT-1300"
         | 
| 406 | 
            +
                    fill_in "City", with: "Mos Eisley"
         | 
| 407 | 
            +
                    select "United States of America", from: "Country"
         | 
| 408 | 
            +
                    select country.states.first.name, from: "order_bill_address_attributes_state_id"
         | 
| 409 | 
            +
                    fill_in "Zip", with: "12010"
         | 
| 410 | 
            +
                    fill_in "Phone", with: "(555) 555-5555"
         | 
| 411 | 
            +
                  end
         | 
| 412 | 
            +
                  click_on "Save and Continue"
         | 
| 413 | 
            +
             | 
| 414 | 
            +
                  # Delivery
         | 
| 415 | 
            +
                  expect(page).to have_current_path("/checkout/delivery")
         | 
| 416 | 
            +
                  expect(page).to have_content("UPS Ground")
         | 
| 417 | 
            +
                  click_on "Save and Continue"
         | 
| 418 | 
            +
             | 
| 419 | 
            +
                  # Payment
         | 
| 420 | 
            +
                  expect(page).to have_current_path("/checkout/payment")
         | 
| 421 | 
            +
                  choose "Use an existing card on file"
         | 
| 422 | 
            +
                  click_button "Save and Continue"
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                  # Confirm
         | 
| 425 | 
            +
                  expect(page).to have_current_path("/checkout/confirm")
         | 
| 426 | 
            +
                  click_button "Place Order"
         | 
| 427 | 
            +
                  expect(page).to have_content("Your order has been processed successfully")
         | 
| 428 | 
            +
                end
         | 
| 429 | 
            +
             | 
| 430 | 
            +
                it_behaves_like "Stripe Elements invalid payments"
         | 
| 431 | 
            +
              end
         | 
| 432 | 
            +
             | 
| 433 | 
            +
              def within_3d_secure_modal
         | 
| 434 | 
            +
                within_frame "__privateStripeFrame10" do
         | 
| 435 | 
            +
                  within_frame "challengeFrame" do
         | 
| 436 | 
            +
                    yield
         | 
| 437 | 
            +
                  end
         | 
| 277 438 | 
             
                end
         | 
| 278 439 | 
             
              end
         | 
| 279 440 | 
             
            end
         |