Sager for Craft CMS

Templating

craft.sager tells a template whether an order has reached your books, and under what invoice number. It's handy on a customer's order page, in a Commerce email, or in your own CP templates.

Methods

MethodReturns
craft.sager.isConnected()true while Sager has a live connection to Sage
craft.sager.businessName()The name of the connected Sage business, or null
craft.sager.status(order)pending, synced, failed or skipped, or null if Sager has never considered the order
craft.sager.isSynced(order)true once the order has a Sage invoice
craft.sager.invoiceId(order)The Sage invoice's ID (a GUID), or null
craft.sager.invoiceNumber(order)The Sage invoice number, such as SI-1042, or null
craft.sager.documents(order)Every Sage document Sager created for the order, oldest first. An empty array if there are none

Every method that takes order accepts an order or an order ID.

isSynced() and status() can disagree for a moment. isSynced() is about whether an invoice exists. status() is about the order's last sync, so an order with an invoice can briefly be pending again while a later payment is queued.

The document object

documents() returns plain arrays, one per document:

KeyWhat it is
documentTypeinvoice, payment or credit_note
sageIdThe document's ID in Sage
sageNumberSage's number for it, such as the invoice or credit note number. Payments have none
amountThe amount, as a decimal string
currencyThe currency code
transactionIdThe Commerce transaction behind a payment or credit note, or null for an invoice
dateCreatedWhen Sager created it, as a UTC date string from the database

The payload Sager sent isn't included. It holds the customer's addresses, and a front-end template never needs it. The full request is in Sager → Log for anyone allowed to see it.

Load the order safely

None of these methods checks who's asking. Pass an order your template has already loaded and verified, never an ID taken straight from the query string. With craft.sager.invoiceNumber(craft.app.request.getParam('id')), anyone could read invoice numbers by counting upwards.

Look the order up by its number, the long unguessable hash Commerce puts in order links, and check it belongs to the logged-in customer:

{% set number = craft.app.request.getQueryParam('number') %}
{% set order = number ? craft.orders()
    .number(number)
    .isCompleted(true)
    .one() : null %}

{% if not order or (currentUser and order.customerId != currentUser.id) %}
    {% exit 404 %}
{% endif %}

An invoice line on the order page

{% if craft.sager.isSynced(order) %}
    {% set invoiceNumber = craft.sager.invoiceNumber(order) %}
    <p>
        Invoiced{% if invoiceNumber %} as <strong>{{ invoiceNumber }}</strong>{% endif %}.
    </p>
{% endif %}

Credit notes for refunds

{% set credits = craft.sager.documents(order)|filter(d => d.documentType == 'credit_note') %}

{% if credits is not empty %}
    <h3>Credit notes</h3>
    <ul>
        {% for credit in credits %}
            <li>
                {{ credit.sageNumber ?: 'Credit note' }}:
                {{ credit.amount|commerceCurrency(credit.currency ?: order.currency) }}
            </li>
        {% endfor %}
    </ul>
{% endif %}

In an email

Commerce emails are rendered with the order, so the same calls work in an email template. Bear in mind that the order-completed email is sent in the same request that completes the order, while Sager sends the invoice from the queue afterwards. The invoice number won't exist yet in that email. It will in a later status email, such as shipped.

{% set invoiceNumber = craft.sager.invoiceNumber(order) %}
{% if invoiceNumber %}
    Your invoice number is {{ invoiceNumber }}.
{% endif %}

A warning in your own CP template

{% if not craft.sager.isConnected() %}
    <p class="error">Sager isn't connected to Sage. Orders aren't reaching the books.</p>
{% elseif craft.sager.status(order) == 'failed' %}
    <p class="error">
        This order didn't reach {{ craft.sager.businessName() ?? 'Sage' }}.
        <a href="{{ cpUrl('sager/orders/' ~ order.id) }}">See why</a>.
    </p>
{% endif %}