Skip to main content

Understanding Orders

Orders in the Scoffable API contain rich information about customer purchases, payments, and any modifications that occur during fulfilment. This page explains the key concepts and data structures you'll encounter.

Overview

Each order is a complete snapshot containing:

  • Items purchased (with options)
  • Payment information
  • Any modifications (substitutions, price adjustments)
  • Status and version tracking

Orders evolve over time through versioning — each change creates a new version with updated information.

Order Versioning

Every modification to an order creates a new version. Each version is a complete snapshot of the order at that moment, not just the changes.

  • Version 1: Created when the order is placed
  • Version 2+: Created for any subsequent change

New versions are created when:

  • Order status changes (accepted, rejected, cancelled)
  • Items are substituted
  • Prices are adjusted
  • Customer or vendor modifies the order
  • Internal processing updates occur

Note that version numbers may increment even without visible changes due to internal processing. Older versions remain unchanged, preserving the historical state of the order at each point in time.

Order Lifecycle

Orders progress through different statuses during their lifecycle:

placed ───> accepted <──> cancelled
│ ▲
└────────────┘
rejected

Order Statuses

  • placed: The initial status when an order is created
  • accepted: The vendor has accepted the order for fulfilment
  • rejected: The vendor declined the order or failed to acknowledge it
  • cancelled: The order was initially accepted but later cancelled by either the vendor or customer

Key Differences

  • rejected vs cancelled: Rejected orders were never accepted, while cancelled orders were accepted first
  • Processing implications: Most integrations skip placed and rejected orders, only processing accepted or cancelled orders

Status Transitions

Important transition rules:

  • No return to placed or rejected: Once an order leaves these states, it can never return to them
  • rejectedaccepted: Can occur if Scoffable initially failed to receive vendor acknowledgement, or if the vendor changes their mind
  • cancelledaccepted: Rare, but possible if cancellation was in error or due to communication issues

The placed status only occurs at order creation, and rejected can only happen before acceptance. Once accepted, an order can only move between accepted and cancelled.

Understanding these transitions is important for handling edge cases in your integration.

Order Items

The items array contains everything ordered, including products, discounts, and adjustments. Each item has a type field:

  • product: Physical items ordered by the customer
  • offer: Discounts or promotions (e.g., "buy 2 get 1 free")
  • voucher: Vouchers applied by the customer
  • adjustment: Post-order modifications (refunds, price corrections)

Quantity Fields

Every item tracks two quantities:

  • quantityOrdered: What the customer originally requested
  • quantityFulfilled: What was actually delivered

These typically match, but differ for substitutions or out-of-stock items.

Option Categories and Options

Many items can be customised through option categories. The optionCategories array in the order shows only the customer's selections, not all available options:

{
"name": "Set meal for 2",
"type": "product",
"price": {"amount": 1999, "currency": "GBP"},
"total": {"amount": 2149, "currency": "GBP"},
"optionCategories": [
{
"name": "Starters",
"selectedOptions": [
{"name": "Spring Rolls"},
{"name": "Prawn Toast"}
]
},
{
"name": "Mains",
"selectedOptions": [
{"name": "Sweet and Sour Chicken"},
{
"name": "Crispy Shredded Duck",
"optionPrice": {"amount": 150, "currency": "GBP"}
}
]
}
]
}

In this example, the menu item might have offered many starter choices (Spring Rolls, Prawn Toast, Spare Ribs, Soup, etc.), but the order only shows what the customer selected. Categories with no selections won't appear at all.

Key Points

  • Only selected options appear in the order
  • Unselected option categories are omitted entirely
  • Each category shows the customer's specific choices
  • Standard options have no additional cost
  • Premium options include optionPrice for the extra charge
  • The item's base price includes standard options but not premium additions

Price Adjustments

Price corrections appear as separate adjustment items linked to the original products:

Adjusted Product

{
"id": "2d4f50ed-de8e-4296-b7c5-9b5704cc6240",
"name": "Set meal for 2",
"type": "product",
"price": {"amount": 1999, "currency": "GBP"},
"priceAdjustmentDetails": {
"relatedPriceAdjustment": "18bcee32-48d7-444b-a374-866f1caa6d02"
}
}

Adjustment Entry

{
"id": "18bcee32-48d7-444b-a374-866f1caa6d02",
"name": "Price match",
"type": "adjustment",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": -400, "currency": "GBP"},
"priceAdjustmentDetails": {
"itemsAdjusted": ["2d4f50ed-de8e-4296-b7c5-9b5704cc6240"]
}
}

Complex Example: Partial Adjustment

When a customer orders multiple quantities and only some need adjustment, the items are split:

// Originally ordered as quantity 2, now split into separate items
// Undamaged pizza (no adjustment)
{
"id": "c3d4e5f6-7890-1234-5678-90abcdef1234",
"name": "Margherita Pizza",
"type": "product",
"quantityOrdered": 1,
"quantityFulfilled": 1,
"price": {"amount": 1299, "currency": "GBP"},
"total": {"amount": 1299, "currency": "GBP"}
}

// Damaged pizza (with adjustment)
{
"id": "e5f6a7b8-9012-3456-7890-bcdef1234567",
"name": "Margherita Pizza",
"type": "product",
"quantityOrdered": 1,
"quantityFulfilled": 1,
"price": {"amount": 1299, "currency": "GBP"},
"total": {"amount": 1299, "currency": "GBP"},
"priceAdjustmentDetails": {
"relatedPriceAdjustment": "d4e5f6a7-8901-2345-6789-0abcdef12345"
}
}

// Adjustment for the damaged pizza
{
"id": "d4e5f6a7-8901-2345-6789-0abcdef12345",
"name": "Compensation for damaged item",
"type": "adjustment",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": -1299, "currency": "GBP"},
"total": {"amount": -1299, "currency": "GBP"},
"priceAdjustmentDetails": {
"itemsAdjusted": ["e5f6a7b8-9012-3456-7890-bcdef1234567"]
}
}

In this example, the customer originally ordered 2 pizzas. When one was damaged, the order splits into two separate items so the adjustment can link to only the affected item.

Substitutions

When ordered items are unavailable, vendors may substitute them with alternatives. The substitution system is flexible and supports various scenarios including one-to-one, many-to-one, one-to-many, and many-to-many substitutions.

Simple One-to-One Substitution

The most common case - one item replaced with another:

Original Item (Not Delivered)

{
"id": "2d4f50ed-de8e-4296-b7c5-9b5704cc6240",
"name": "Galaxy 200g",
"type": "product",
"quantityOrdered": 1,
"quantityFulfilled": 0,
"price": {"amount": 399, "currency": "GBP"},
"substitutionDetails": {
"substitutedBy": ["5114d08f-bb16-4fad-b992-f43682665b40"]
}
}

Substitute Item (Delivered Instead)

{
"id": "5114d08f-bb16-4fad-b992-f43682665b40",
"name": "Dairy Milk 200g",
"type": "product",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": 399, "currency": "GBP"},
"substitutionDetails": {
"substitutedFor": ["2d4f50ed-de8e-4296-b7c5-9b5704cc6240"]
}
}

Complex Substitutions

The substitutedBy and substitutedFor fields are arrays, enabling complex substitution patterns:

Many-to-One Example

Four small chocolate bars replaced with one large bar:

// Four small bars (not delivered)
{
"id": "8f2e3d4c-9a1b-4567-8901-23456789abcd",
"name": "Small Chocolate Bar 50g",
"quantityOrdered": 4,
"quantityFulfilled": 0,
"substitutionDetails": {
"substitutedBy": ["a1b2c3d4-e5f6-4789-0123-456789abcdef"]
}
}

// One large bar (delivered instead)
{
"id": "a1b2c3d4-e5f6-4789-0123-456789abcdef",
"name": "Large Chocolate Bar 200g",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"substitutionDetails": {
"substitutedFor": ["8f2e3d4c-9a1b-4567-8901-23456789abcd"]
}
}

One-to-Many Example

One item replaced with multiple different items:

// Original variety pack (not delivered)
{
"id": "7c8d9e0f-1234-5678-90ab-cdef12345678",
"name": "Chocolate Variety Pack",
"quantityOrdered": 1,
"quantityFulfilled": 0,
"substitutionDetails": {
"substitutedBy": ["3f4e5d6c-7890-1234-5678-90abcdef1234", "9a8b7c6d-5432-1098-7654-321098765432"]
}
}

// Multiple different substitutes (delivered instead)
{
"id": "3f4e5d6c-7890-1234-5678-90abcdef1234",
"name": "Milk Chocolate Bar 100g",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"substitutionDetails": {
"substitutedFor": ["7c8d9e0f-1234-5678-90ab-cdef12345678"]
}
},
{
"id": "9a8b7c6d-5432-1098-7654-321098765432",
"name": "Dark Chocolate Bar 100g",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"substitutionDetails": {
"substitutedFor": ["7c8d9e0f-1234-5678-90ab-cdef12345678"]
}
}

Note: If substituting with multiples of the same item (e.g., two Regular Pack 200g), this would appear as a single item with quantityFulfilled: 2, not as separate items.

Many-to-Many Example

Multiple wine bottles replaced with a different combination due to availability:

// Original wine order (not available)
{
"id": "4e5f6a7b-8901-2345-6789-abcdef123456",
"name": "Pinot Grigio 750ml",
"quantityOrdered": 2,
"quantityFulfilled": 0,
"price": {"amount": 899, "currency": "GBP"},
"substitutionDetails": {
"substitutedBy": ["1a2b3c4d-5678-9012-3456-789012345678", "2b3c4d5e-6789-0123-4567-890123456789"]
}
},
{
"id": "5f6a7b8c-9012-3456-7890-bcdef1234567",
"name": "Sauvignon Blanc 750ml",
"quantityOrdered": 1,
"quantityFulfilled": 0,
"price": {"amount": 999, "currency": "GBP"},
"substitutionDetails": {
"substitutedBy": ["1a2b3c4d-5678-9012-3456-789012345678", "2b3c4d5e-6789-0123-4567-890123456789"]
}
}

// Substitute wines (delivered instead)
{
"id": "1a2b3c4d-5678-9012-3456-789012345678",
"name": "Mixed White Wine Case (6x250ml)",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": 1899, "currency": "GBP"},
"substitutionDetails": {
"substitutedFor": ["4e5f6a7b-8901-2345-6789-abcdef123456", "5f6a7b8c-9012-3456-7890-bcdef1234567"]
}
},
{
"id": "2b3c4d5e-6789-0123-4567-890123456789",
"name": "Chardonnay 750ml",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": 899, "currency": "GBP"},
"substitutionDetails": {
"substitutedFor": ["4e5f6a7b-8901-2345-6789-abcdef123456", "5f6a7b8c-9012-3456-7890-bcdef1234567"]
}
}

In this example, the customer ordered 3 bottles of wine (2 Pinot Grigio + 1 Sauvignon Blanc). Due to availability, the vendor substituted with a mixed case of smaller bottles plus a full-size Chardonnay, providing equivalent volume. Both substitutes reference all original items as they collectively replace the entire wine order.

Complex Example: Substitution with Price Adjustment

A common pattern is an item being substituted and then price-adjusted to match the original:

// Original item ordered (not delivered)
{
"id": "b5c6d7e8-9012-3456-7890-abcdef123456",
"name": "Standard Chocolate 100g",
"type": "product",
"quantityOrdered": 1,
"quantityFulfilled": 0,
"price": {"amount": 299, "currency": "GBP"},
"total": {"amount": 299, "currency": "GBP"},
"substitutionDetails": {
"substitutedBy": ["f1e2d3c4-5678-9012-3456-789012345678"]
}
}

// Premium substitute delivered
{
"id": "f1e2d3c4-5678-9012-3456-789012345678",
"name": "Premium Chocolate 100g",
"type": "product",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": 499, "currency": "GBP"},
"total": {"amount": 499, "currency": "GBP"},
"substitutionDetails": {
"substitutedFor": ["b5c6d7e8-9012-3456-7890-abcdef123456"]
},
"priceAdjustmentDetails": {
"relatedPriceAdjustment": "a9b8c7d6-3456-7890-1234-567890abcdef"
}
}

// Price adjustment to match original price
{
"id": "a9b8c7d6-3456-7890-1234-567890abcdef",
"name": "Substitution price match",
"type": "adjustment",
"quantityOrdered": 0,
"quantityFulfilled": 1,
"price": {"amount": -200, "currency": "GBP"},
"total": {"amount": -200, "currency": "GBP"},
"priceAdjustmentDetails": {
"itemsAdjusted": ["f1e2d3c4-5678-9012-3456-789012345678"]
}
}

In this example:

  • Customer ordered a £2.99 item
  • Vendor substituted with a £4.99 premium item
  • A -£2.00 adjustment ensures customer pays the original £2.99
  • All three items appear in the order for complete tracking

Substitution Pricing Scenarios

When substitutes have different prices, the pricing can be handled in different ways:

Scenario 1: Price Matched (Via Adjustment)

If a £3.99 item is substituted with a £4.99 alternative at no extra cost:

  1. Original item shows quantityFulfilled: 0
  2. Substitute item shows quantityFulfilled: 1 with its actual price (£4.99)
  3. An adjustment item of -£1.00 is added to maintain the original price

Scenario 2: Customer Pays Higher Price

If the customer agrees to a premium substitution:

  1. Original item shows quantityFulfilled: 0 at £3.99
  2. Substitute shows quantityFulfilled: 1 at £4.99
  3. No price adjustment needed
  4. Scoffable may collect the additional £1.00 from the customer
  5. The order total increases accordingly

Scenario 3: Scoffable Goodwill

In some cases, Scoffable may absorb the cost difference:

  1. Substitute is delivered at higher price
  2. Customer still pays original total
  3. Scoffable covers the difference as goodwill

Processing Substitutions

  • Check quantityFulfilled to identify what was actually delivered
  • Use substitutionDetails to link original and replacement items
  • Check for price adjustments to understand the pricing approach
  • Compare order total with sum of fulfilled items to identify goodwill scenarios
  • The final customer payment may differ from the original order value

Common Price Adjustment Scenarios

  • Adjusting substitute prices to match original items
  • Price matching competitors
  • Compensation for damaged items
  • Correction of pricing errors
  • Partial refunds

Customer Payments

The customerPayments array shows how the order was paid and who collected the funds:

"customerPayments": [
{
"type": "cash",
"collectedBy": "vendor",
"payment": {"amount": 1899, "currency": "GBP"}
},
{
"type": "voucher",
"collectedBy": "scoffable",
"payment": {"amount": 500, "currency": "GBP"}
}
]

Payment Types

  • online: Card or digital payment
  • cash: Physical cash payment
  • voucher: Scoffable credit redemption (promotional or purchased gift vouchers)

Collection Responsibility

  • scoffable: Payment processed through the platform
  • vendor: Payment collected directly by the vendor

This distinction is critical for financial reconciliation — it determines who holds the funds and who owes whom.

Vouchers and Promotions

Vouchers can appear in two different places, representing different types of promotions:

Vendor-Funded Promotions (Item)

When a voucher appears as an item, it's a promotion funded by the vendor:

{
"name": "10% off promotion",
"type": "voucher",
"quantityOrdered": 1,
"quantityFulfilled": 1,
"price": {"amount": -500, "currency": "GBP"}
}

The vendor bears the cost of this discount.

Scoffable Vouchers (Payment)

When a voucher appears in customerPayments, it's a Scoffable-issued voucher used as payment:

{
"type": "voucher",
"collectedBy": "scoffable",
"payment": {"amount": 500, "currency": "GBP"}
}

Scoffable has already collected payment for this voucher (e.g., from a corporate client or previous purchase) and will settle with the vendor.

Understanding the Distinction

  • Item voucher: Vendor gives discount, receives less money
  • Payment voucher: Customer uses Scoffable credit, vendor receives full payment (from Scoffable)

Delivery Information

Orders with type: "delivery" include additional delivery-related fields:

Delivery Provider

The deliveryProvider field indicates who handles the delivery:

  • vendor: The vendor manages their own delivery
  • uber: Delivery handled through Uber's courier network

Delivery Tracking

Delivery tracking availability depends on the provider:

  • Uber delivery: Usually provides tracking updates and deliveredAt timestamps
  • Vendor delivery: May not provide tracking or deliveredAt times if the vendor doesn't use tracked deliveries

This means for vendor-managed deliveries, you might never receive a deliveredAt timestamp, and should plan your integration accordingly.

Financial Reconciliation

Key fields for reconciliation:

  • serviceFee: Fee charged to the customer on behalf of the vendor
  • deliveryFee: Delivery charge (when applicable)
  • total: Final order amount the customer pays (includes service fee and delivery fee)
  • customerPayments: How the order was paid and who collected it
  • Order Sync - Integration patterns for synchronising orders
  • API Reference - Detailed endpoint specifications
  • FAQ - Common questions about order statuses