Shipping Rates estimator
Hi :),
It would be nice to have a native support for shipping rates around the Shopify shipping rates API.
Thanks!
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:
If you need any details don't hesitate. I can't wait to have this feature, that will make CartJS definitely complete :)
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?
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 :).
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 has this been implemented at all? thanks! Matthew
@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 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
@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 :).
this is of interest