cartjs icon indicating copy to clipboard operation
cartjs copied to clipboard

Shipping Rates estimator

Open bakura10 opened this issue 10 years ago • 9 comments

Hi :),

It would be nice to have a native support for shipping rates around the Shopify shipping rates API.

Thanks!

bakura10 avatar Jun 29 '15 12:06 bakura10

Hi @gavinballard ,

As part of this feature, I'd like to highlight some useful things that we would need (that I have build a bit manually with Rivets in Kagami):

  • Having access to the status of the shipping estimation (failed or success).
  • If failed, being able to have access to the error message
  • If success, being able to have access to the list of the shipping rates
  • In Kagami, we are also displaying the cheapest shipping rate just above the "Checkout" button for better experience:
capture d ecran 2015-09-11 a 09 51 42

If you need any details don't hesitate. I can't wait to have this feature, that will make CartJS definitely complete :)

bakura10 avatar Sep 11 '15 07:09 bakura10

Hey @bakura10 I'm going to take a look at this soonish. Would you be able to share the manual code you wrote for this?

gavinballard avatar Jan 24 '16 10:01 gavinballard

Sure.

Not really good but first, I've added a new model when initializing CartJS (should not be needed if integrated by default):

CartJS.init({{ cart | json }}, {
        currency: {{ shop.currency | json }},
        moneyFormat: {{ shop.money_format | json }},
        moneyWithCurrencyFormat: {{ shop.money_with_currency_format | json}},
        rivetsModels: {
          shipping: {
            is_submitting: false,
            has_rates: false,
            has_errors: false,
            errors: [],
            rates: [],
            first_price: 0
          }
        }
      });

Then, my code just updates the model and fill errors, shipping rates (do not forget to initialize the province selector too):

var shippingEstimator = $('.shipping-estimator');

  $('.shipping-estimator__submit').on('click', function() {
    CartJS.settings.rivetsModels.shipping['is_submitting'] = true;

    $.ajax({
      method: 'GET',
      url: '/cart/shipping_rates.json',
      data: {
        shipping_address: {
          country: shippingEstimator.find('#address_country').val(),
          province: shippingEstimator.find('#address_province').val(),
          zip: shippingEstimator.find('#address_zip').val()
        }
      }
    }).done(function(results) {
      // Shopify return rates already formatted in the shop currency, but we need to multiply them by 100
      results['shipping_rates'].forEach(function(item, index) {
        results['shipping_rates'][index]['price'] *= 100;
      });

      if (results['shipping_rates'].length > 0) {
        CartJS.settings.rivetsModels.shipping.first_price = results['shipping_rates'][0]['price'];
      }

      CartJS.settings.rivetsModels.shipping['is_submitting'] = false;
      CartJS.settings.rivetsModels.shipping['has_rates'] = true;
      CartJS.settings.rivetsModels.shipping['has_errors'] = false;
      CartJS.settings.rivetsModels.shipping['rates'] = results['shipping_rates'];
    }).error(function(results) {
      var response = results.responseJSON,
        errors = [];

      for (var key in response) {
        if (response.hasOwnProperty(key)) {
          errors.push({key: key, value: response[key][0]});
        }
      }

      CartJS.settings.rivetsModels.shipping['is_submitting'] = false;
      CartJS.settings.rivetsModels.shipping['has_rates'] = true;
      CartJS.settings.rivetsModels.shipping['has_errors'] = true;
      CartJS.settings.rivetsModels.shipping['errors'] = errors;
    });

    return false;
  });

  new Shopify.CountryProvinceSelector('address_country', 'address_province', {hideElement: 'address_province_container'});

Then, I have my shipping estimator snippet (just to show how I'm using all the rivets variables):

<section class="shipping-estimator" data-cart-view="data-cart-view" rv-show="cart.item_count | gt 0" {% if cart.item_count == 0 %}style="display: none"{% endif %}>
  <h3 class="shipping-estimator__title">{{ 'cart.shipping_estimator.title' | t }}</h3>

  <div class="shipping-estimator__form">
    <div class="form__control shipping-estimator__country">
      <label class="form__label" for="address_country">{{ 'cart.shipping_estimator.country' | t }}</label>

      <div class="form__select">
        {% include 'icon' with 'arrow-bottom' %}
        <select id="address_country" name="shipping_address[country]" data-default="{% if shop.customer_accounts_enabled and customer %}{{ customer.default_address.country }}{% elsif settings.cart_shipping_estimator_default_country != '' %}{{ settings.cart_shipping_estimator_default_country }}{% endif %}">{{ country_option_tags }}</select>
      </div>
    </div>

    <div class="form__control shipping-estimator__province" id="address_province_container" style="display: none;">
      <label class="form__label" for="address_province" id="address_province_label">{{ 'cart.shipping_estimator.province' | t }}</label>

      <div class="form__select">
        {% include 'icon' with 'arrow-bottom' %}
        <select id="address_province" name="shipping_address[province]" data-default="{% if shop.customer_accounts_enabled and customer and customer.default_address.province != '' %}{{ customer.default_address.province }}{% endif %}"></select>
      </div>
    </div>

    <div class="form__control shipping-estimator__zip">
      <label class="form__label" for="address_zip">{{ 'cart.shipping_estimator.zip' | t }}</label>
      <input type="text" id="address_zip" name="shipping_address[zip]"{% if shop.customer_accounts_enabled and customer %} value="{{ customer.default_address.zip }}"{% endif %} required="required">
    </div>

    <div class="form__control">
      <button type="submit" rv-disabled="shipping.is_submitting" class="shipping-estimator__submit button button--secondary">
        <span rv-show="shipping.is_submitting" style="display: none">{{ 'cart.shipping_estimator.submitting' | t }}</span>
        <span rv-hide="shipping.is_submitting">{{ 'cart.shipping_estimator.submit' | t }}</span>
      </button>
    </div>
  </div>

  <div class="shipping-estimator__results" rv-show="shipping.has_rates" style="display: none">
    <div class="alert alert--error" rv-show="shipping.has_errors">
      <h4 class="alert__title">{{ 'cart.shipping_estimator.error' | t }}</h4>

      <ul class="alert__errors">
        <li rv-each-error="shipping.errors">
          {error.key}: {error.value}
        </li>
      </ul>
    </div>

    <div class="alert alert--success" rv-hide="shipping.has_errors">
      <h4 class="alert__title" rv-unless="shipping.rates | empty">{{ 'cart.shipping_estimator.rates' | t }}</h4>
      <h4 class="alert__title" rv-if="shipping.rates | empty">{{ 'cart.shipping_estimator.no_rates' | t }}</h4>

      <ul class="alert__errors">
        <li rv-each-rate="shipping.rates">
          {rate.name}: <span rv-html="rate.price | money Currency.currentCurrency"></span>
        </li>
      </ul>
    </div>
  </div>
</section>

The variable "first_price" (could be made differently, for instance if CartJS would include a new | first and | last formatters) is used in the Cart next to the total to display the estimated shipping:

        <span class="cart__taxes" rv-hide="shipping.rates | empty" style="display: none">{{ 'cart.general.estimated_taxes' | t }} <span rv-html="shipping.first_price | money Currency.currentCurrency"></span></span>

Let me know if you have any question :).

bakura10 avatar Jan 24 '16 11:01 bakura10

Thanks @bakura10 - this is really useful. Will probably ping you for feedback on the WIP branch once I get around to working on this. :)

gavinballard avatar Jan 25 '16 11:01 gavinballard

@gavinballard has this been implemented at all? thanks! Matthew

ghost avatar Apr 26 '17 13:04 ghost

@devmpet Not yet! I have approximately zero time to contribute to CartJS at the moment, although I'm always happy to review pull requests and bump new versions.

gavinballard avatar Apr 26 '17 13:04 gavinballard

@gavinballard completely appreciate that - just wanted to check I was not about to work on something that had already been done but not released yet. Really appreciate the work put into CartJS - very versatile and well thought out! Cheers

ghost avatar Apr 26 '17 13:04 ghost

@devmpet No worries, didn't want that to come off as defensive - just a fact of life at the moment unfortunately! Would love to look at a PR if what you're working on ends up working well :).

gavinballard avatar Apr 26 '17 13:04 gavinballard

this is of interest

capatina avatar Dec 19 '20 19:12 capatina