Skip to content

Orders — acceptance criteria

Placing an order

10 scenarios — these are acceptance criteria for what the service must do, not a description of what it does.

binding spec features/place-order.feature @ 2.1.0 — the spec of record. It states what must be; implementations converge on it, never the reverse.

Enforced by
  • check:version — any change must bump the artifact and service versions; a silent edit fails CI
  • bound by implementations in strict mode — an unbound scenario fails their build
  • check:intent — every schema element added to a contract must be named here
  • gherkin-lint
Version history
VersionFirst releasedService version
2.1.02026-08-293.1.0
2.0.02026-08-273.0.0
1.3.02026-08-242.1.0
1.2.02026-08-232.0.0
1.1.02026-08-231.0.0
Consume it

As a customer I want to place an order So that it is fulfilled once payment settles These scenarios are acceptance criteria, not suggestions. An implementation that fails any of them is wrong. If a scenario is itself wrong, that is a versioned change to this file.

A valid order is accepted and its event is guaranteed

  • Whenthe customer places an order for 2 units of SKU-RED at 1250 pence
  • Thenthe response status is 201
  • Andthe order status is placed
  • Andthe order total is 2500 pence
  • Andan OrderPlaced event is guaranteed to be published for the order

The order and its event succeed or fail together

  • Givenevent publication is unavailable
  • Whenthe customer places an order for 1 unit of SKU-RED at 1250 pence
  • Thenthe response status is 201
  • Andthe OrderPlaced event is published once publication recovers
  • Andno order ever exists without its OrderPlaced event

Replaying an idempotency key returns the original order

  • Giventhe customer placed an order with idempotency key idem-99
  • Whenthe same request is retried with idempotency key idem-99
  • Thenthe response status is 201
  • Andthe same order_id is returned
  • Andno second OrderPlaced event is published

A conflicting body on a used idempotency key is rejected

  • Giventhe customer placed an order with idempotency key idem-99
  • Whena different order body is sent with idempotency key idem-99
  • Thenthe response status is 409

Order events are CloudEvents envelopes

  • Whenthe customer places an order for 2 units of SKU-RED at 1250 pence
  • Thenan OrderPlaced event is published on orders.placed.v2
  • Andthe envelope carries specversion 1.0, a unique id and a time
  • Andthe envelope source is /orders and its type is com.hungovercoders.orders.placed.v2
  • Andthe envelope subject is the order_id, with datacontenttype application/json
  • Andthe data carries the order_id, customer_id, placed_at and total_pence
  • Andhandlers dedupe on the envelope id, not the natural key

Cancellation is a CloudEvents envelope too

  • Giventhe customer placed an order
  • Whenthe order is cancelled because out_of_stock
  • Thenan OrderCancelled event is published on orders.cancelled.v2
  • Andits data carries the order_id, cancelled_at and reason

A settled payment marks the order paid

  • Giventhe customer placed an order
  • Whena PaymentSettled event arrives on payments.settled.v2 for that order_id
  • Thenthe order status is paid
  • Andreplaying the same envelope id does not change the order again

An order can be fetched by its id

  • Giventhe customer placed an order via placeOrder
  • Whenthe order is fetched by order_id via getOrder
  • Thenthe response status is 200
  • Andthe order carries the order_id, customer_id, status and total_pence

Fetching an unknown order returns 404

  • Whenan unknown order_id is fetched via getOrder
  • Thenthe response status is 404

Orders must have at least one valid line

  • Whenthe customer places an order with <lines>
  • Thenthe response status is 400
  • Examples
  • lines
    no lines
    a line quantity of 0
    a negative unit_price_pence
    a line missing its sku
Raw Gherkin
Feature: Placing an order
  As a customer
  I want to place an order
  So that it is fulfilled once payment settles

  These scenarios are acceptance criteria, not suggestions. An
  implementation that fails any of them is wrong. If a scenario is
  itself wrong, that is a versioned change to this file.

  Background:
    Given the customer "c-1001" exists

  Scenario: A valid order is accepted and its event is guaranteed
    When the customer places an order for 2 units of "SKU-RED" at 1250 pence
    Then the response status is 201
    And the order status is "placed"
    And the order total is 2500 pence
    And an "OrderPlaced" event is guaranteed to be published for the order

  Scenario: The order and its event succeed or fail together
    Given event publication is unavailable
    When the customer places an order for 1 unit of "SKU-RED" at 1250 pence
    Then the response status is 201
    And the "OrderPlaced" event is published once publication recovers
    And no order ever exists without its "OrderPlaced" event

  Scenario: Replaying an idempotency key returns the original order
    Given the customer placed an order with idempotency key "idem-99"
    When the same request is retried with idempotency key "idem-99"
    Then the response status is 201
    And the same order_id is returned
    And no second "OrderPlaced" event is published

  Scenario: A conflicting body on a used idempotency key is rejected
    Given the customer placed an order with idempotency key "idem-99"
    When a different order body is sent with idempotency key "idem-99"
    Then the response status is 409

  Scenario: Order events are CloudEvents envelopes
    When the customer places an order for 2 units of "SKU-RED" at 1250 pence
    Then an "OrderPlaced" event is published on "orders.placed.v2"
    And the envelope carries specversion "1.0", a unique id and a time
    And the envelope source is /orders and its type is com.hungovercoders.orders.placed.v2
    And the envelope subject is the order_id, with datacontenttype "application/json"
    And the data carries the order_id, customer_id, placed_at and total_pence
    And handlers dedupe on the envelope id, not the natural key

  Scenario: Cancellation is a CloudEvents envelope too
    Given the customer placed an order
    When the order is cancelled because "out_of_stock"
    Then an "OrderCancelled" event is published on "orders.cancelled.v2"
    And its data carries the order_id, cancelled_at and reason

  Scenario: A settled payment marks the order paid
    Given the customer placed an order
    When a "PaymentSettled" event arrives on "payments.settled.v2" for that order_id
    Then the order status is "paid"
    And replaying the same envelope id does not change the order again

  Scenario: An order can be fetched by its id
    Given the customer placed an order via placeOrder
    When the order is fetched by order_id via getOrder
    Then the response status is 200
    And the order carries the order_id, customer_id, status and total_pence

  Scenario: Fetching an unknown order returns 404
    When an unknown order_id is fetched via getOrder
    Then the response status is 404

  Scenario Outline: Orders must have at least one valid line
    When the customer places an order with <lines>
    Then the response status is 400

    Examples:
      | lines                       |
      | no lines                    |
      | a line quantity of 0        |
      | a negative unit_price_pence |
      | a line missing its sku      |