---
title: The Order Lifecycle
description: "Every purchase in RackHelm is stored as an **order** that holds one or more **order items** — the things the admin area calls *packages*. Understanding the lifecycle matters because RackHelm tracks state in three separate columns, not one, and a…"
url: "https://rackhelm.com/documentation/order-lifecycle/"
updated: "2026-09-24"
category: "Orders & Provisioning"
---

# The Order Lifecycle

Every purchase in RackHelm is stored as an **order** that holds one or more **order items** — the things the admin area calls *packages*. Understanding the lifecycle matters because RackHelm tracks state in three separate columns, not one, and a screen that looks wrong is usually two of those columns disagreeing. This article is for operators and staff.

## The three status columns

An order and its items each carry their own state, and the item carries a second, independent state for the machine work behind it:

| Column | Lives on | Answers |
| --- | --- | --- |
| `orders.status` | The order (the billing container) | Is this order live, finished, or cancelled? |
| `order_items.status` | Each package and each addon | Is the customer entitled to this service right now? |
| `order_items.provisioning_status` | Each package and each addon | Has the service actually been built on the remote system? |

> **Note:** An order has no total column. Its value is always recalculated as `(unit_price + setup_fee) × quantity` across its items, less `discount_amount`. What the customer was actually charged lives on the invoice, not the order.

## Order statuses

| Status | What it means and what sets it |
| --- | --- |
| `pending` | Written the instant a cart is converted into an order at checkout. It is transient: the same checkout transaction immediately moves the order to `active` when the invoice is raised. You will normally only see it on imported records. |
| `active` | The normal live state. Set at checkout once invoices are generated, and the starting state for an order you create from **Create Order** in the admin area. |
| `completed` | A finished order that will not recur. Set when a *Credit Deposit* order settles (the money becomes account balance), and used by the legacy client importer for packages it imports as active, suspended or terminated. |
| `pending_cancellation` | Set automatically when *every* item on the order is pending cancellation. |
| `cancelled` | Set automatically when *every* item on the order is cancelled. |

The last two are never set by hand: `Order::syncStatusWithItems()` derives them from the items, and it runs after a customer cancellation, after a termination job succeeds, and whenever the cancellation sweeper processes an item.

Paying an invoice does not change the order's status. It advances `orders.due_date` by the billing cycle length, anchored on the invoice's due date rather than today, so paying early never shortens the term the customer bought.

## Package (order item) statuses

| Status | What it means and what sets it |
| --- | --- |
| `active` | The database default, so every item created at checkout starts here. The customer is entitled to the service. |
| `pending` | Items created through **Create Order** in the admin area start here rather than at `active`. It is not one of the model's four named constants, but it is what the admin creation path writes. |
| `suspended` | Service withheld, usually for non-payment. Set by the overdue-suspension sweeper, or by an operator on the package screen. |
| `pending_cancellation` | Cancellation requested; the service keeps running until `expires_at`, which is set to the order's due date at the moment of the request. |
| `cancelling` | Teardown is in flight — a termination job has been dispatched and has not yet reported back. |
| `cancelled` | Teardown finished. Written by the termination job when the remote provider confirms. |
| `terminated` | Only produced by the legacy client importer, for packages that were already terminated in the source system. |

Only items at `active` are billable — the model's `billable` scope filters on exactly that — so a suspended or cancelling package is not picked up when invoices are generated.

## Provisioning statuses

| Status | What it means |
| --- | --- |
| `pending` | The column default. Waiting to be picked up by the provisioning scanner. |
| `queued` | Claimed by the scanner and handed to a worker. `provisioning_attempts` is incremented at this point. |
| `processing` | A worker has the item and is talking to the remote system. Also the resting state for an addon flagged for manual provisioning. |
| `provisioned` | Built successfully. This is what the Active Packages list renders as **Active**. |
| `failed` | The provisioner threw, or the remote system refused. The reason is stored in `provisioning_error` (truncated to 2,000 characters) and shown on the package screen. |
| `awaiting_configuration` | SSL certificates only. The certificate is bought but the customer has not yet supplied a CSR and chosen a validation method. |
| `manual` | An addon bought at checkout whose definition is marked for manual provisioning. |
| `active` | An addon bought at checkout whose definition does not need provisioning at all. |
| `suspended` | Written by the suspend job once the remote provider confirms the service is off. |
| `terminated` | Written by the terminate job once the remote provider confirms the service is gone. |

> **Warning:** Neither `manual` nor `active` is picked up by the provisioning scanner, and neither appears on the Pending Provisioning screen. A `manual` addon only becomes visible there once its parent package finishes provisioning, at which point the pipeline moves it to `processing`.

![The Active Packages list showing status badges such as Active, Provisioning, Suspended and Failed](/wp-content/uploads/rackhelm-docs/orders-lifecycle-package-badges.png)
Active Packages blends both item columns into one badge — the package status wins whenever it is suspended, pending cancellation or cancelled.

## How the two item columns are combined on screen

The Active Packages list does not show the raw columns. It derives one badge:

1. It starts from `provisioning_status` — `provisioned` is relabelled **Active**, and `pending`, `queued` and `processing` all collapse into **Provisioning**.
2. The package's own status then overrides it: `suspended` shows **Suspended**, `pending_cancellation` shows **Pending Cancel**, and `cancelled` shows **Cancelled**.

The order detail screen is the opposite: it shows both columns side by side, in separate **Status** and **Provisioning** columns, so it is the place to go when you need to see exactly what each column holds.

![An order detail page listing items with separate Status and Provisioning columns](/wp-content/uploads/rackhelm-docs/orders-order-show-item-statuses.png)
The order detail screen shows the two item columns separately, with the failure reason under a failed provisioning badge.

## Deleting is a soft delete

Both orders and order items are soft-deleted. Deleting a package from a client's package list removes it from every active listing and reconciles its outstanding invoices, but the row is retained for audit and reporting. Once an order has no remaining items it is soft-deleted too.

Two consequences worth knowing:

- Order numbers are never reused. The next number is calculated including deleted orders, so a deleted order's number cannot collide with a future invoice number.
- The termination job deliberately loads items including deleted ones, so choosing "also terminate the live remote service" during a delete still tears the service down even though the row is already gone from normal queries.

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| A package shows **Active** but the customer has nothing | `provisioning_status` is `provisioned` while nothing was actually built — most often an item marked provisioned by hand. Check `provisioned_external_id` on the package: if it is empty, no remote object was created. |
| An order sits at `pending` long after checkout | The order was created but the invoice generation step did not complete. Check the queue monitor for a failed `GenerateInvoiceJob`. |
| A cancelled package still bills | The order's status only follows the items when *all* of them are cancelled. Check the remaining items on the order. |
| Status changed but the customer's dashboard is unchanged | The customer view keys off `provisioning_status`, not the package status — `pending`, `queued` and `processing` all render as "Setting up". Change the provisioning state, not just the package state. |
