Commerce v1 Upgrades v0.11

Most upgrades are automatically applied, but sometimes you may need to update custom templates, or status configuration, to take advantage of new features or improvements. Those changes are discussed here.

Most changes listed in this migration document are optional.

Product Images

Commerce 0.11 saw the release of primary product images. This allows you to add one image to products to show it in the cart/checkout.

For this purpose, the default template frontend/checkout/cart/items.twig was slightly updated.

  • Around line 6, a new empty header was added: <th id="c-cart-header-image">&nbsp;</th>
  • Around line 16, the colspan wrapping the cart update button was changed from 5 to 6.
  • Around line 24, the following column was added to show the image:
<td class="c-cart-item-image">
    {% if item.image %}
        <img src="{{ item.image }}" alt="{{ item.name }}" onerror="this.style.display = 'none';">
    {% endif %}
</td>

If you're using Resource Products, a new commerce.resourceproduct.image_field system setting allows you to connect an image TV to the Commerce products table.

If you have a custom product type that does not inherit the fields from comProduct, look at core/c../c../model/commerce/comproduct.class.php around line 299 for the field definition used in the standard commerce form.

For custom product types that fetch information from a different table/source look at comresourceproduct.class.php around line 118 for the new getImage() method you can also override in your product type. Make sure to also update your synchronise method.

Shipping Methods (Shipments)

In v0.11 we also refactored the built-in shipping methods to base calculations on the order shipments, instead of the full order. This applies to both the pricing and availability.

This was announced in 0.8 and is now complete. While it should be backwards compatible, please verify any custom shipping methods still work as expected.

Continuing off-site payments with POST redirect

If the client for some reason finds themselves back on the checkout while the transaction isn't confirmed/failed/cancelled yet, there was previously a redirect link to continue. This however does not work when the gateway requires a POST-style redirect (e.g. Adyen).

To correct this, the frontend/checkout/pending-transaction.twig was updated in 0.11 to allow such transactions to continue as well. Around line 7, change the following:

{% if transaction.properties.redirectUrl %}
    <a class="c-button" href="{{ transaction.properties.redirectUrl }}">{{ lex('commerce.transaction_pending_to_gateway', { method: method.name }) }}</a>
{% endif %}

to:

{% if transaction.properties.redirectUrl %}
    <form method="POST" action="{{ current_url }}" class="commerce-pending-transaction-form commerce-pending-transaction-form-retry">
        <input type="hidden" name="retry" value="1">
        <button class="c-button" type="submit">{{ lex('commerce.transaction_pending_to_gateway', { method: method.name }) }}</button>
    </form>
{% endif %}

For AJAX request handling in the checkout, you'll need to make sure to look at the response.redirectMethod and response.redirectData to also support these redirects.

Automatically select first shipping/payment method

Small but useful tweak in frontend/checkout/shipping-method.twig to autoselect the first shipping method automatically if none were previously selected.

Around line 43 change:

{% if shipment.method == method.id %}checked="checked"{% endif %}

to:

{% if shipment.method == method.id %}
    checked="checked"
{% elseif shipment.method < 1 and loop.first %}
    checked="checked"
{% endif %}

Similary in frontend/checkout/payment-method.twig around line 14 add:

{% if loop.first %}checked="checked"{% endif %}

to the input with name choose_payment_method. With some new formatting applied, that should now look like:

<input  type="radio" 
        name="choose_payment_method" 
        class="c-method-radio c-payment-method-radio" 
        id="payment-method-{{ method.id }}" 
        value="{{ method.id }}" 
        {% if loop.first %}checked="checked"{% endif %}
>

Configurable email header and footer

0.11 introduces 2 new settings, commerce.email_header_url and commerce.email_footer_text, which automatically adds an image heading and the email footer text from a setting, without having to change the email templates.

If you have a custom emails/wrapper.twig template, you'll want to add the following to support the new header image around line 116 (instead of the previously commented out code):

{% if config.email_header and config.email_header|length > 0 %}
<tr>
    <td bgcolor="#ffffff" align="center">
        <!--[if (gte mso 9)|(IE)]>
        <table align="center" border="0" cellspacing="0" cellpadding="0" width="500">
            <tr>
                <td align="center" valign="top" width="500">
                    <![endif]-->
                    <table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 500px;" class="wrapper">
                        <tr>
                            <td align="center" valign="top" style="padding: 15px 0;" class="logo">
                                <a href="{{ config.site_url }}" target="_blank">
                                    <img alt="{{ config.site_name }}" src="{{ config.email_header }}" width="500" height="120" style="display: block; font-family: Helvetica, Arial, sans-serif; color: #ffffff; font-size: 16px;" border="0">
                                </a>
                            </td>
                        </tr>
                    </table>
                <!--[if (gte mso 9)|(IE)]>
                </td>
            </tr>
        </table>
        <![endif]-->
    </td>
</tr>
{% endif %}

For the footer text, around line 150 which previously only had {{ config.site_name }}, add the following:

{% if config.email_footer|length > 0 %}
    {{ config.email_footer|raw }}
{% else %}
    {{ config.site_name }}
{% endif %}

Shipping method pre-selected in cart

Commerce will now pre-select the first available shipping method for an order, potentially as soon as the cart. To show the shipping method correctly, you may want to update your frontend/checkout/cart/totals.twig template around line 78.

Previous:

{% if order.shipping != 0 %}
    {% set totalIndex = totalIndex + 1 %}
    <tr class="{% if totalIndex is odd %}c-cart-total-item{% else %}c-cart-total-item-even{% endif %}">
        <th class="c-cart-totals-label c-cart-totals-label-shipping">
            {{ lex('commerce.shipping') }}
        </th>
        <td class="c-cart-totals-shipping">
            {{ order.shipping_formatted }}
        </td>
    </tr>
{% endif %}

New:

{% for shipment in shipments %}
    {% if shipment.method.id > 0 %}
        {% set totalIndex = totalIndex + 1 %}
        <tr class="{% if totalIndex is odd %}c-cart-total-item{% else %}c-cart-total-item-even{% endif %}">
            <th class="c-cart-totals-label c-cart-totals-label-shipping c-cart-totals-label-shipment">
                {{ shipment.method.name }}
            </th>
            <td class="c-cart-totals-value c-cart-totals-shipping">
                {{ shipment.fee_formatted }}
            </td>
        </tr>
    {% endif %}
{% endfor %}

This functionality looks at the sort order of the shipping methods, so make sure you have them in the right order under Configuration > Shipping methods.

Taxed shipping costs

0.11 now applies tax rules to the shipping costs. Because of this we're reordered a few elements in frontend/checkout/cart/totals.twig and frontend/checkout/partial/summary.twig to make sure the numbers are in the order people would expect.

In frontend/checkout/cart/totals.twig, the subtotal block was removed from the {% if order.discount != 0 %} conditional around line 5 and the related {% else %} around line 32, to be placed ahead of that entire conditional around line 4 instead.

The shipments block around line 78 was moved to after the subtotal block, and before the discount conditional, around line 13.

In frontend/checkout/partial/summary.twig, the shipments block was moved from around line 84 to around line 65 (before the {% if order.tax != 0 %} conditional), and an extra check was added to show the fee inclusive or exclusive of VAT, like so:

{% for shipment in shipments %}
    {% if shipment.method.id > 0 %}
        <tr class="c-cart-summary-totals-row-shipping c-cart-summary-totals-row-shipment">
            <th class="c-cart-summary-totals-label c-cart-summary-totals-label-shipping c-cart-summary-totals-label-shipment">
                {{ shipment.method.name }}
            </th>
            <td class="c-cart-summary-totals-value c-cart-summary-totals-shipping">
                {% if tax_exclusive %}
                    {{ shipment.fee_ex_tax_formatted }}
                {% else %}
                    {{ shipment.fee_incl_tax_formatted }}
                {% endif %}
            </td>
        </tr>
    {% endif %}
{% endfor %}

As Commerce 0.11 has had quite a few shipping-related template tweaks, it may be worthwhile to grab the default templates and to re-apply any tweaks you made if you get stuck following these instructions.

When an order item is created, it sets the item's link value to the result of product->getLink. But in frontend/checkout/cart/items.twig it was still delegating to product->getLink (item.product.link) instead of the saved value (item.link).

0.11 ships with a new ItemData module that can set the link on standard products (which don't normally have that, as a single product could be used on different resources; vs a resource product type that has the 1-1 connection). This 0.11 module can be used to set the item's link without the product having an associated link, so this issue finally became obvious.

If you plan to use the item-specific links, edit your theme's frontend/checkout/cart/items.twig template to have:

{% if item.link %}
    <a href="{{ item.link }}" title="{{ item.name }}">{{ item.name }}</a>
{% else %}
    <b>{{ item.name }}</b>
{% endif %}

instead of

{% if item.product.link %}
    <a href="{{ item.product.link }}" title="{{ item.name }}">{{ item.name }}</a>
{% else %}
    <b>{{ item.name }}</b>
{% endif %}

The default checkout summary doesn't currently have links, but if you do have that included in your own theme, make sure to also update it there.

You have one items in cart

In rc2 we changed the frontend/checkout/cart.twig template to distinguish between having one or multiple items in the cart, to improve the linguistics of the translations.

Change the header to:

<h2>
    {% if order.total_quantity != 1 %}
        {{ lex('commerce.cart_header', { 'items': order.total_quantity }) }}
    {% else %}
        {{ lex('commerce.cart_header_single') }}
    {% endif %}
</h2>

Note that not all translations will have been updated when rc2 was released, so it's possible you'll see the multiple in your own language, but the single in English.

(0.11.1) Field errors repeated above the address form

The frontend/response-messages.twig template shows the errors for the checkout, however on the address validation it would also show individual field errors.

In 0.11.1 the template was adjusted to the following to skip field-specific errors with the assumption that they are shown inline with the form:

{% if response.message|length %}
    <div class="c-cart-message">
        <p>{{ response.message }}</p>
    </div>
{% endif %}
{% if response.errors|length > 0 %}
    {% for error in response.errors %}
        {% if error.field == '' %}
            <div class="c-cart-error">
                {{ error.message }}
            </div>
        {% else %}
            {#
                Field errors are usually handled in the form, but if you want them to be shown at the top, you can use something like this:

                <div class="c-cart-error">
                    {{ lex('commerce.address.' ~ error.field) }}: {{ error.message }}
                </div>
            #}
        {% endif %}
    {% endfor %}
{% endif %}

(0.11.1) New AcceptTerms module

A new AcceptTerms module was added in 0.11.1 which, when enabled, requires the following code to be added to the frontend/checkout/payment-method.twig template. Where is up to you, but usually right before the submit button (line 45) is a good place.

{% if module_accept_terms_enabled %}
    <label class="c-accept-terms">
        <input type="checkbox" name="accept_terms" value="1" required>
        {{ lex('commerce.module.acceptterms.text', {url: module_accept_terms_url}) }}
    </label>
{% endif %}

(0.11.3) Fix label in billing address fields

The for attribute on the VAT Registration billing address field was wrong.

If you have a custom frontend/checkout/partial/billing-address-fields.twig template, change the following on line 24:

<label for="address-billing-company">{{ lex('commerce.vat_registration') }} <small>{{ lex('commerce.address.optional') }}</small></label>

to:

<label for="address-billing-vat-registration">{{ lex('commerce.vat_registration') }} <small>{{ lex('commerce.address.optional') }}</small></label>

(0.11.4) Note: get_product now looks at the first valid product

Previously the commerce.get_product snippet only looked at the very first product ID listed. If that product was removed or invalid, it would not return any product at all. Now, the snippet will use all product IDs to find the first option that still exists.

In normal usage, this should not cause any changes in behaviour, but it's worth double checking your catalog to make sure it will still behave the way you expect it to.