Orders

Orders represent complete customer transactions—from cart contents and pricing through payment and fulfillment. Each order captures what was purchased, who bought it, how much they paid, and whether payment succeeded. Orders serve as the authoritative record connecting line items, customers, payments, and invoices into a single coherent transaction.


Why orders matter

Orders solve the coordination problem between shopping carts, payments, and fulfillment. Without orders, you're manually linking payment records to product lists, reconstructing customer intent from fragments, and building custom tracking for every transaction.

Immutable transaction records

Once an order is finalized (sealed), its line items and total become immutable. The customer sees exactly what they agreed to pay, accounting systems have reliable totals for reconciliation, and dispute resolution has definitive evidence of what was purchased. This immutability enables trustworthy audit trails and eliminates pricing ambiguity.

Payment coordination

Orders orchestrate payment execution from initiation through completion. When you create an order, Commerce automatically creates the associated payment, validates the total matches line items, and manages the state transitions as payment progresses. Your code watches order status rather than coordinating payment state manually.

Clear fulfillment workflow

Orders separate payment from fulfillment through distinct states. When payment succeeds, check the payment's paid status to trigger fulfillment. When fulfillment completes, mark the order as completed. This separation lets you track both financial and operational milestones accurately.

Built-in invoicing

Every finalized order automatically generates an invoice with customer details, line item breakdowns, and totals. The invoice serves as both a receipt and—when needed—a hosted payment page where customers select payment methods and complete checkout in a Commerce-managed interface.


Order lifecycle

Orders use a state-machine architecture that enforces clear transitions between stages. This design prevents invalid operations (like shipping before payment), ensures auditability (every transition is logged), and makes integration predictable—your application responds to well-defined state changes rather than coordinating complex payment flows manually. Orders progress from preparing (editing line items) to requires_payment (finalized and ready for payment) to paid (funds captured) to completed (fulfilled).

Understanding how orders transition between states—and how order status differs from payment status—is essential for building reliable checkout and fulfillment flows. See the Order Lifecycles guide for detailed state transitions, payment next actions at each stage, and the relationship between order status and payment status.


Creating and finalizing orders

Orders begin in the preparing state where line items can be modified, then transition to requires_payment when finalized. Understanding the finalization options helps you build the right checkout flow.

Basic order creation

Create an order with the Create an Order endpoint:

curl -X POST https://api.zebo.dev/orders/new \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "ORDER-001",
    "customer_data": {
      "name": "Kwame Mensah",
      "phone_number": "+233241234567",
      "email_address": "kwame@example.com"
    },
    "line_items": [
      {
        "type": "product",
        "product": {
          "name": "Premium Widget",
          "reference": "WIDGET-001",
          "price": {"currency": "ghs", "value": 10000},
          "quantity": 2,
          "type": "physical",
          "category": "electronics"
        }
      }
    ]
  }'

This creates an order in preparing state. Line items can still be modified (though the API doesn't currently expose modification endpoints—create a new order instead).

Finalization options

You control when an order finalizes through three parameters:

finalize: true – Explicitly finalize the order during creation. Line items become immutable, invoice is generated, but payment isn't executed. Use this when you want the order finalized but need to initiate payment later.

execute_payment: true – Finalize the order AND initiate payment execution. This is automatic execution mode—one API call creates, finalizes, and starts payment. Use this for standard checkout flows where the customer expects immediate payment processing.

Important: execute_payment: true requires a payment method. You must include either payment_method_id (for saved methods) or payment_method_data (for new methods) in the same request. Without a payment method, there's nothing to execute.

Neither flag – Order stays in preparing state. Finalize later by calling the Finalize an Order endpoint or the Pay for an Order endpoint.

The key distinction: finalize makes the order immutable and generates the invoice. execute_payment does both of those AND starts payment execution. Since execution requires a payment method, you must provide one when using execute_payment: true.

Finalize and pay later

Create an order without finalization, then finalize and pay in separate steps:

# Step 1: Create order
curl -X POST https://api.zebo.dev/orders/new \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"number": "ORDER-002", "customer_data": {...}, "line_items": [...]}'

# Step 2: Finalize order (optional - makes it immutable)
curl -X POST https://api.zebo.dev/orders/finalize \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"order_id": "order_xyz123"}'

# Step 3: Execute payment
curl -X POST https://api.zebo.dev/orders/pay \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"order_id": "order_xyz123"}'

Note: Calling the Pay for an Order endpoint automatically finalizes the order if it isn't already sealed. The explicit Finalize an Order endpoint step is optional—useful when you want to lock line items before payment execution.


Payment execution and confirmation

Once an order is finalized, its associated payment progresses through execution, authorization, and settlement. Understanding payment states and next actions is critical for building checkout flows that handle confirmation tokens and provider authorization.

Payment execution

When you create an order with execute_payment: true or call the Pay for an Order endpoint, Commerce executes the payment. Payment execution requires a payment method—either pass payment_method_id (saved method) or payment_method_data (new method details).

With payment method provided – Commerce initiates payment immediately. For mobile money, this may trigger SMS confirmation (if configured). For cards, this may redirect to 3D Secure. Check the payment's next_action to determine what happens next.

Without payment method – If you don't provide a payment method when creating the order, don't use execute_payment: true. Instead, finalize the order to get a hosted checkout page where the customer selects and enters payment details. The checkout URL is automatically available in the invoice.format.web.url field of the finalized order.

Payment next actions

After executing payment, check the order's payment.next_action field to determine what your application must do:

confirm_payment – Customer must confirm transaction intent. Commerce sent a confirmation token (typically via SMS) to the customer's phone. Your application must collect this token from the customer and submit it via the Confirm a Payment endpoint. This verification step prevents unauthorized charges—the customer proves they initiated the transaction by retrieving the token from their SMS.

authorize_payment – Customer must authorize payment with their provider. For mobile money, this means entering their PIN in the provider's USSD prompt. For cards, this means completing 3D Secure authentication. Your role is passive—display a waiting state and poll the Lookup an Order endpoint to detect when authorization completes.

redirect – Payment requires browser redirect. The next_action.redirect object contains the URL. Redirect the customer to that URL (typically a bank authentication page). After completing authentication, the provider redirects back to your configured redirect_url.

No next action – Payment is processing or complete. Poll the Lookup an Order endpoint to track progress. When payment status becomes paid, funds are captured and you can fulfill the order.

Confirmation flow for mobile money

Mobile money payments often require explicit customer confirmation:

  1. Create order with execute_payment: true and mobile money payment method
  2. Commerce generates 6-digit token and sends via SMS to customer's phone
  3. Payment's next_action becomes confirm_payment
  4. Your UI prompts customer to check SMS and enter the token
  5. Submit token via the Confirm a Payment endpoint
  6. If token is correct, payment advances to authorize_payment next action
  7. Customer enters PIN in mobile money USSD prompt
  8. Poll the Lookup an Order endpoint until payment status becomes paid

Customers have 5 attempts to enter the correct confirmation token. After 5 failures, the confirmation locks and you must request a new confirmation via the Request Confirmation endpoint.

Polling for completion

Payment execution is asynchronous. Never assume instant completion:

// Start payment
await commerce.orders.pay({ order_id: order.id })

// Poll until payment succeeds
let paid = false
while (!paid) {
  await new Promise(resolve => setTimeout(resolve, 3000)) // Wait 3 seconds
  
  const order = await commerce.orders.lookup({ order_id: order.id })
  
  if (order.payment?.status === 'paid') {
    // Payment succeeded - trigger fulfillment
    paid = true
  } else if (order.status === 'canceled' || order.status === 'expired') {
    // Order terminated - handle error
    paid = true
  }
  
  // Check for required actions
  if (order.payment?.next_action?.type === 'confirm_payment') {
    // Prompt customer for confirmation token
    const token = await promptForToken()
    await commerce.orders.confirmPayment({
      order_id: order.id,
      token: token
    })
  }
}

Poll every 3-5 seconds initially. Implement exponential backoff if payment extends beyond 60 seconds, but continue polling for at least 5 minutes—mobile money authorization can occasionally take 2-3 minutes during provider issues.


Line items and totals

Orders are containers for line items—products, fees, and shipping charges. Understanding line item types and total calculation is essential for building accurate checkout flows.

Line item types

product – Goods or services the customer purchases. Products have name, price, quantity, reference ID, category, and custom metadata. Each product contributes price × quantity to the total.

fee – Fixed charges like processing fees or service charges. Fees have a flat amount (not multiplied by quantity) and a descriptive label.

shipping – Delivery charges. Functions like a fee but semantically distinct. Orders support at most one shipping line item.

tax and discount – Reserved for future use. To apply taxes or discounts today, use fee line items with appropriate amounts.

Total calculation

When an order is finalized, Commerce computes the total from line items:

  1. Sum all product amounts: Σ(product.price × product.quantity)
  2. Add shipping fee: + shipping.fee
  3. Add all fees: + Σ(fee.amount)

The resulting computed_total becomes immutable after finalization. This total determines what the payment charges.

Currency consistency

All line items must use the same currency. Commerce validates this during order creation. If you add a product in GHS and a fee in USD, order creation fails. Multi-currency transactions require separate orders.


Invoices and hosted checkout

Every finalized order automatically generates an invoice. Invoices serve dual purposes: receipts showing what was purchased, and—when needed—hosted payment pages where customers complete checkout.

Automatic invoice generation

Finalization triggers invoice generation. The invoice captures:

  • Order number and timestamps
  • Your business details (name, legal entity type)
  • Customer details (name, contact info)
  • Line item breakdown with quantities and prices
  • Total amount with currency
  • Payment status

Invoices update automatically as order status changes. Call the Lookup an Order endpoint to force regeneration if needed.

Hosted checkout pages for payment

When you create an order without specifying a payment method, Commerce automatically generates a hosted checkout page when the order is finalized. Customers visit this page to select a payment method, enter details, and complete payment—all in the Commerce interface.

The checkout URL is available in the finalized order response:

curl -X POST https://api.zebo.dev/orders/new \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "finalize": true,
    "customer_data": {"name": "Jane Doe"},
    "line_items": [...]
  }'

After payment completes, Commerce redirects the customer back to your configured redirect_url with the order ID as a query parameter. Check the order status to confirm payment succeeded.

Use hosted checkout when you want Commerce to handle payment UI and PCI compliance. Perfect for simple integrations or mobile-first experiences.


Customer and payment method handling

Orders require a customer (who's buying) and may require a payment method (how they'll pay). You can create these inline during order creation or reference existing records.

Providing customer information

New customer – Pass customer_data with name, phone, and/or email. Commerce creates the customer record and links it to the order. Use this for guest checkout or first-time buyers.

Existing customer – Pass customer_id to reference a previously created customer. Commerce validates the customer exists and belongs to your application.

Providing payment methods

New payment method – Pass payment_method_data with method type and details (like mobile money number and network). Commerce creates the payment method, saves it to the customer, and attaches it to the order.

Existing payment method – Pass payment_method_id to reference a saved payment method. Commerce validates it belongs to the specified customer.

No payment method – Omit both parameters. Commerce generates a Zebo Checkout page where the customer selects and enters their payment method. Control which methods appear with payment_method_types.

Payment method types

Use payment_method_types to restrict allowed payment methods:

{
  "payment_method_types": ["mobile_money", "card"],
  ...
}

If you provide a specific method via payment_method_id or payment_method_data, Commerce uses that method regardless of payment_method_types.


API operations

The Orders API provides endpoints for creating, finalizing, paying, and managing orders:

Create an Order – Create a new order with line items and customer info. Optionally finalize and execute payment in one call.

Lookup an Order – Retrieve an order by ID. Returns current status, line items, payment state, and next actions.

Finalize an Order – Finalize an existing order. Makes line items immutable and generates invoice without executing payment.

Pay for an Order – Execute payment for an order. Automatically finalizes if not already sealed. Initiates payment with attached method or generates Zebo Checkout.

Confirm a Payment – Submit customer confirmation token. Required when payment's next_action is confirm_payment.

Request Confirmation – Manually request a new confirmation token. Use when customer didn't receive initial SMS or exhausted retry attempts.

Complete an Order – Mark an order as fulfilled and complete. Call this after the customer receives their items or you deliver the service. Payment must be successful before completion. For offline payments (cash, bank transfer), optionally pass paid_out_of_band: true to mark the payment as paid in the same request.

Page Through Orders – List orders with pagination. Filter by status, customer, date range, or other criteria.

Was this page helpful?