Orders
What an order is, its lifecycle, statuses, providers, line items, and amounts.
Overview
An order is the customer's cart. It represents the grouping of products, taxes, discounts, and tips that a customer is purchasing from a merchant. The order tracks what was bought, the amounts owed, and whether the customer has paid.
Orders are the base layer of the Decal platform. Every payment — whether initiated through a payment link, the API, or a POS terminal — ultimately creates or updates an order. The order itself is not the payment: it describes what the customer is buying and how much they owe. The actual movement of money is tracked separately by internal transaction records.
{
"id": "ord_live_a1b2c3d4",
"provider": "native",
"status": "open",
"paymentStatus": "unpaid",
"currency": "USD",
"items": [
{
"name": "Premium Plan (Annual)",
"quantity": 1,
"unitPrice": 9900,
"totalPrice": 9900
}
],
"amounts": {
"subtotal": 9900,
"tax": 0,
"discount": 0,
"tip": 0,
"total": 9900,
"paid": 0
},
"createdAt": "2026-04-12T14:30:00Z",
"updatedAt": "2026-04-12T14:30:00Z"
}Order lifecycle
Orders have two orthogonal status fields that track different things:
status tracks the order's fulfillment lifecycle:
| Status | Meaning |
|---|---|
open | Order exists and is active. May be unpaid, partially paid, or fully paid. |
completed | Order is fulfilled and closed. |
cancelled | Order was voided or abandoned. Cannot be reopened. |
paymentStatus tracks how much the customer has paid relative to the total:
| Payment Status | Meaning |
|---|---|
unpaid | No payment received. |
partially_paid | Some payment received, but less than the total. |
paid | Customer paid the full amount (or more). |
overpaid | Customer paid more than the total (e.g., included a tip beyond the order total). |
These two fields are independent. An order can be open and paid (payment received but not yet
fulfilled), or completed and paid (the typical terminal state).
┌─────────────┐
│ open │
│ unpaid │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌────────┐ ┌───────────┐
│ open │ │ open │ │ open │
│ partially │ │ paid │ │ cancelled │
│ paid │ │ │ │ unpaid │
└─────┬─────┘ └───┬────┘ └───────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ open │ │ completed │
│ paid │ │ paid │
└─────┬─────┘ └───────────┘
│
▼
┌───────────┐
│ completed │
│ paid │
└───────────┘An order transitions to completed after payment is confirmed and any fulfillment actions (like
notifying an external POS or loading stored value) finish successfully. An unpaid order can be
cancelled if the customer abandons checkout or the merchant voids it.
What's in an order
An order is composed of line items and financial totals:
Line items
Each item in the order has a name, quantity, unit price, and computed total price. Items can optionally include a description, image URL, product reference, and arbitrary metadata.
{
"items": [
{
"name": "Cappuccino",
"quantity": 2,
"unitPrice": 550,
"totalPrice": 1100
},
{
"name": "Blueberry Muffin",
"quantity": 1,
"unitPrice": 400,
"totalPrice": 400
}
]
}Amounts
All monetary amounts are in the smallest currency unit (cents for USD). This avoids
floating-point rounding issues that plague decimal-based calculations. Amounts are grouped under the
amounts object on every order:
| Field | Meaning | Example |
|---|---|---|
subtotal | Sum of line item totals | 1500 ($15.00) |
tax | Total tax applied | 128 ($1.28) |
discount | Total discount applied | 200 ($2.00) |
tip | Optional customer gratuity | 0 |
total | subtotal + tax - discount + tip — what the customer owes | 1428 ($14.28) |
paid | What has actually been received | 0 (unpaid) |
amounts.paid is the source of truth for paymentStatus. When a payment is confirmed,
amounts.paid is updated to reflect the actual amount received, and paymentStatus is
recalculated:
amounts.paid == 0→unpaid0 < amounts.paid < amounts.total→partially_paidamounts.paid >= amounts.total→paid(oroverpaidif strictly greater)
Taxes and discounts
Orders can include itemized tax and discount lines, each with a name, type, value, and scope:
Taxes have three types:
additive— added on top of the subtotal (e.g., 8.5% sales tax)inclusive— already included in item prices (e.g., VAT)fixed— a flat amount
Discounts have two types:
fixed— a flat dollar amount offpercentage— a percentage of the subtotal
Both taxes and discounts can be scoped to the entire order or to individual line_items.
Order providers
The provider field indicates where the order originated:
| Provider | Description |
|---|---|
native | Created through Decal's systems or API. Line items, taxes, and discounts are stored in the Decal platform. |
square | Synced from a Square POS terminal. The full Square order is stored as a snapshot; line items are extracted from that snapshot rather than stored in relational tables. |
The provider determines how payment completion is communicated back to the originating system. For native orders, payment is recorded entirely within Decal.
For external platform orders (e.g. Square), Decal also records the payment the external platform's APIs so the POS reflects the completed transaction.
Customer information
Orders capture customer information as a point-in-time snapshot at payment time:
{
"customer": {
"email": "alice@example.com",
"phone": "+15551234567",
"name": "Alice Chen"
},
"shippingAddress": {
"firstName": "Alice",
"lastName": "Chen",
"address1": "123 Main St",
"city": "Brooklyn",
"state": "NY",
"postalCode": "11215",
"country": "US",
"phone": "+15551234567"
}
}The customer snapshot is immutable after payment — even if the customer later updates their profile, the order reflects who they were when they paid. This ensures receipts and records remain accurate.
Shipping and billing addresses are optional by default, but can be marked as required to ensure customers must enter all required information for their order (e.g. shipping address when you are taking payments for physical products that must be shipped to the customer).
Test vs. live mode
Every order ID encodes its mode: ord_test_* or ord_live_*. Test and live orders are fully
segregated:
- Test orders use sandbox payment methods and never settle real money. Use test mode during development and integration testing.
- Live orders process real payments and settle to the merchant's account.
A test order cannot become a live order or vice versa. The mode is set at creation and is part of the order's identity.
Creating orders
Orders are created through two paths:
Via payment link (most common)
When a customer opens a payment link, an order is created automatically as part of the checkout session. The order's line items, price, and currency come from the link's configuration. This is the standard merchant flow: create a link, share it, customers pay.
Via API
For programmatic use cases, create orders directly:
POST /v0/orders{
"mode": "live",
"items": [
{
"name": "Custom Widget",
"quantity": 3,
"unitPrice": 2500
}
],
"customerEmail": "bob@example.com"
}Use direct API creation when you need full control over the order composition — for example, when building a custom cart experience or creating orders from a backend system.