First step is to make a small liquid tweak to hide our pricing line items inline, so that when our price fix javascript runs, we don't get a very noticeable collapsing of the item list table as we remove the pricing item lines. Edit layout/checkout.liquid
and find the following:
<div class="sidebar__content"> ##{{ content_for_order_summary }} </div>
Replace that with:
<div class="sidebar__content"> ##{{ content_for_order_summary | replace: 'data-product-type="SHOPSTORM_HIDDEN_PRODUCT"', 'data-product-type="SHOPSTORM_HIDDEN_PRODUCT" style="display:none;"' }} </div>
Then, insert the following javascript to fix the main product prices just before the </body>
closing tag (this is important, as it won't work properly in the document head):
<script type="text/javascript"> // http://help.shopstorm.com/article/420-shopify-plus-checkout-premium-installation // Version: 3 // Date: 2017-08-03 // Assumptions: // * We assume a certain page structure, and bail out aggressively if things // aren't as we expect (e.g. Shopify changed the layout) // * Each pricing item is proceeded by an associated main product // * An event named 'page:change' is fired on the document each time the // checkout page is ajax-refreshed // * Line item prices use a dot as decimal separator (1,299.99) or no decimals. // If decimal separator is comma (1.299,99), code will NOT work. // Hide any pricing items, and add their cost to the associated main product line function fixPaidCustomizationProducts(node) { // Money (not cents) string w/o currency. Only digits, commas, and dots are preserved. // e.g. '$50.00' becomes '50.00', '$1,234.99' becomes '1,234.99' // @param moneyString [String] // @return [String] var getMoneyStringWithoutCurrency = function(moneyString) { return moneyString.replace(/[^\d.,]/g, ''); }; // Money (not cents) number. // e.g. '$50.00' becomes 50, '$1,234.99' becomes 1234.99 // @param moneyString [String] // @return [Number, NaN] Number if conversion went well, NaN otherwise. var getMoneyNumber = function(moneyString) { return +getMoneyStringWithoutCurrency(moneyString).replace(/[^\d.]/g, ''); }; // @param element [HTMLElement] will have its textContent replaced. // @param rawPriceString [String] money string, without currency // @param pricingItemLinePrice [Number] price to add to raw price string var replacePriceTextAddingCustomization = function(element, rawPriceString, pricingItemLinePrice) { var priceCustomized = (getMoneyNumber(rawPriceString) + pricingItemLinePrice).toFixed(2); element.textContent = element.textContent.replace(rawPriceString, priceCustomized); }; var pricingItemRows = node.querySelectorAll('[data-product-type="SHOPSTORM_HIDDEN_PRODUCT"]'); for (var i = 0; i < pricingItemRows.length; i++) { var pricingItemRow = pricingItemRows[i]; var productPriceEl = pricingItemRow.getElementsByClassName('product__price')[0]; if (!productPriceEl) { return; } // Pricing item line price as money (not cents) Number, e.g. '$50.00' becomes 50, '$1.99' becomes 1.99 var pricingItemLinePrice = getMoneyNumber(productPriceEl.textContent); if (!pricingItemLinePrice) { return; } var mainItemRow = pricingItemRow.previousElementSibling; if (!mainItemRow) { return; } var mainItemLinePriceEl = mainItemRow.getElementsByClassName('product__price')[0]; if (!mainItemLinePriceEl) { return; } // Main item line price element may include a crossed out original price (usually in a <del> tag) // which results in a non-numeric string, e.g. '106.0090.10'. We need // the final line price, assumed to be in the last child element. var mainItemLinePriceStrikeThru = mainItemLinePriceEl.getElementsByTagName('del')[0]; var mainItemLinePriceLastChild = mainItemLinePriceEl.lastElementChild; var mainItemLinePriceRaw = getMoneyStringWithoutCurrency(mainItemLinePriceLastChild.textContent); if (isNaN(+mainItemLinePriceRaw)) { return; } // update the main product price if (!mainItemLinePriceLastChild) { return; } replacePriceTextAddingCustomization( mainItemLinePriceLastChild, mainItemLinePriceRaw, pricingItemLinePrice ); if (mainItemLinePriceStrikeThru) { console.debug(mainItemLinePriceStrikeThru); var mainItemLinePriceStrikeThruRaw = getMoneyStringWithoutCurrency(mainItemLinePriceStrikeThru.textContent); replacePriceTextAddingCustomization( mainItemLinePriceStrikeThru, mainItemLinePriceStrikeThruRaw, pricingItemLinePrice ); } // remove the pricing item row pricingItemRow.parentElement.removeChild(pricingItemRow); } } // fix the paid customization products on page load. fixPaidCustomizationProducts(document); // fix the paid customization products on ajax refresh (this results in a small but noticeable flash as the main product price is fixed) document.addEventListener('page:change', function() { fixPaidCustomizationProducts(document); }); </script>
Less common cases
In addition to the above changes, the plus checkout may include a customized order summary loop summary, which shows the hidden product. For example,
<tr data-product-id="##{{ line.product_id }}" data-variant-id="##{{ line.variant_id }}" data-product-type="##{{ line.product.type }}">
Replace with
<tr data-product-id="##{{ line.product_id }}" data-variant-id="##{{ line.variant_id }}" data-product-type="##{{ line.product.type }}" {% if line.product.type == 'SHOPSTORM_HIDDEN_PRODUCT' %} style="display:none;"{% endif %}>
Additionally, if the main product(s) show the _pc_pricing line item properties at checkout, we have to detect and skip those properties in the line.properties loop.
{% for prop in line.properties %} {%- capture prop_first_char -%}##{{ prop.first | slice: 0, 1 }}{%- endcapture -%} {%- if prop_first_char == '_' -%}{% continue %}{%- endif -%}