Guide 08 · Developer docs

Commerce

A Core dna store is a set of records and an API that moves a purchase from cart to order: products described in every language and priced in every currency the store sells in, a cart, a checkout that becomes an order, invoices, payments, tax and shipping. The site builds the storefront on top: the cart and checkout pages, their scripts, and the customer emails.

L2–L3
Unit 05 · From payment to fulfilment

From checkout to order

What the platform does, step by step, at the moment a paid checkout becomes an order, which record to change from then on, the order's currency and language, and how the customer-facing order number is made.

Who this is for. Developers who build the steps after payment, or connect orders to another system. After this page you know what exists once a checkout is paid, which record to change afterwards, and how to set the format of order numbers.

When a checkout becomes an order

A checkout becomes an order once its invoice is fully paid. Where that happens depends on the payment: in the purchase call itself when the provider settles the whole amount there, and for a free checkout; or later, from the provider's webhook, when the payment completes away from the page (The payment-processing page and webhooks). A checkout whose payment is waiting for authentication or a redirect, or covers only part of the total, is split from its cart and kept until the payment resolves (From cart to order).

What the conversion does, in order

The conversion runs in one database transaction, so it completes as a whole or not at all:

  1. A new order is created with a uuid of its own, not the checkout's.
  2. Every field the two records share is copied to the order: the person, both addresses, the currency, the totals and the rest.
  3. The checkout's lines become the order's lines: each line now belongs to the order and no longer to the cart. The metadata field values, the record of applied discounts and the fulfilments move to the order with them.
  4. The purchased lines leave the cart. A cart with nothing left in it is deleted; a cart that still holds lines the shopper did not check out keeps them for later.
  5. An order number is generated (Order numbers).
  6. The checkout record is removed and the order is saved.
  7. The order's date (ordered) is set, and each invoice of the checkout is re-pointed to the order.
  8. The order's financial status is worked out from its invoices, and the transaction commits.

Most of what the order gathers was made earlier. The fulfilments were created when shipping was chosen, the invoice by the purchase call, and the payment's transactions sit on the invoice. The conversion adds the order itself and its number, and moves the rest across.

The shopper's browser is sent to the order-complete page, /checkout/complete/<order uuid>, with the order's uuid, which the purchase response also carries (The payment-processing page and webhooks).

The record to change after payment

Once the conversion has run, the checkout has done its job and is gone. Everything that happens to the purchase from then on happens to the order and the records around it:

To changeChange this
What the customer paid, or money returnedThe invoice and its transactions, or a refund (Invoices, transactions and refunds)
Where the goods areA fulfilment's status (Stock, fulfilment and the member's order screens)
The order's financial statusIts invoices and refunds: the status is worked out from them every time the order is saved
The order's fulfilment statusIts fulfilments: the status is worked out from them every time the order is saved

Code that keeps a checkout's id or uuid, to find the purchase later, switches to the order's uuid at this point. The money records keep their history: a transaction is final once saved, an invoice moves forward through its statuses and stays once issued, and a refund is final once it has succeeded, failed or been cancelled.

The financial status

getFinancialStatus() returns the value and getFinancialStatusDisplay() the admin's label:

ValueLabel
unpaidUnpaid
authorizedPayment Authorized
partially_paidPartially Paid
paidPaid
partially_refundedPartially Refunded
refundedFully Refunded
voidedPayment Voided

The fulfilment status, and what a customer sees at each stage, are on Stock, fulfilment and the member's order screens.

The order's currency and the language of its lines

The order is in the checkout's currency, copied across with the totals, and so are its invoices and every payment and refund on them (The currency a payment is taken in). Read the code with getCurrencyCode(), and print amounts with the money getters, which format in the order's own currency. Never put a currency symbol in front of an amount by hand: on a multi-currency store the same template shows orders in several currencies.

The order's lines are the checkout's lines, so each name is the one stored when the item was added to the cart: the product's name in the client's default language (Its currency, and the language of its lines). On a multilingual site, print a line in the shopper's language from its product's description, as the member's order screen on Stock, fulfilment and the member's order screens does.

Wherever a template has the order, such as the member's order screen or a job's email template, the summary reads:

Template code
<p>Order {$order->getNumber()|escape}</p>
<p>{$order->getFinancialStatusDisplay()} · {$order->getFulfillmentStatusDisplay()}</p>
<p>Total {$order->getTotalAsMoney()->format()} ({$order->getCurrencyCode()})</p>
<p>Order {{ order.getNumber()|e }}</p>
<p>{{ order.getFinancialStatusDisplay() }} · {{ order.getFulfillmentStatusDisplay() }}</p>
<p>Total {{ order.getTotalAsMoney().format() }} ({{ order.getCurrencyCode() }})</p>

Order numbers

Each order gets a number for the customer, made from the Order number template setting (Orders.orders.numberTemplate), {SEQ} by default. The template is text with placeholders:

PlaceholderBecomes
{SEQ}The next number in the sequence: 1, 2, 3
{SEQ:n}The same, zero-filled to n digits: {SEQ:6} gives 000001
{DATE}Today's date, as 20261021

So ORD-{DATE}-{SEQ:4} gives ORD-20261021-0001, then ORD-20261021-0002. Write {SEQ} or {SEQ:n} into every template: the sequence is what makes each number unique.

How the sequence counts:

  • It counts per site: each site of the account has its own.
  • A template that contains {DATE} starts a fresh sequence every day, so the first order of each day is 1. A template without {DATE} counts on.
  • Each template has its own sequence. Changing the template starts again at 1, as the setting's help warns; changing only the zero-filling ({SEQ} to {SEQ:6}) carries on from the current number.
  • Each number is checked against the site's existing order numbers before it is used.

Settle the format before the first live order, so the numbers customers hold all follow one pattern.

Why a number and not the record id

The record id is the database's key for the order; it is not made for people. The order number is: it is generated separately from the id, counts per site, can carry a prefix and a date that make it recognisable on an invoice or over the phone, and stays the same for the life of the order. Show the number on every screen and in every email. Where code has to find an order, use the uuid or the id, as the order-complete page (the uuid) and the member's order screen (the id, among the signed-in member's own orders) do.

The rest of this unit

Invoices, transactions, refunds and the refund adjustment are on Invoices, transactions and refunds.

Stock, warehouses and inventory transactions, and how a store keeps stock in step with the system that ships its goods, are on Stock, fulfilment and the member's order screens.

Receipts, shipping notices and refund notices are jobs on the order, fulfilment and refund records: see Customer emails.

Go deeper

Was this page helpful?

Your answer goes to the docs team.