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 progress through distinct states from creation through fulfillment. Understanding these states helps you build checkout flows that respond correctly to payment and fulfillment events.

Order states

preparing – Order is open for editing. Add line items, update quantities, change customer details. The order exists but isn't ready for payment—no invoice, no payment possible.

requires_payment – Order is finalized and waiting for payment. Line items are locked, invoice generated, payment ready to execute. This is where payment happens.

paid – Payment succeeded and funds are captured. The order transitions to this status after successful payment. This is your signal to trigger fulfillment—ship items, grant access, or deliver services.

completed – Order is fulfilled and closed. The customer received their items or you provided the service. Mark orders complete via /orders/complete after fulfillment finishes.

canceled – Order was canceled before completion. Cancellation doesn't automatically refund payments—handle refunds separately.

expired – Order reached its 7-day deadline before payment. Create a new order if the customer still wants to purchase.

State transitions

Createpreparing. Orders start here by default. Add line items and customer details.

Finalizerequires_payment. Lock line items and enable payment. Three ways:

  • Pass finalize: true when creating
  • Pass execute_payment: true when creating (also starts payment—requires payment method)
  • Call /orders/finalize on existing order

Pay → Payment becomes paid. Execute payment via /orders/pay or execute_payment: true. Check the payment's status (not order status) to confirm funds captured.

Fulfillcompleted. After fulfilling the order (shipping items, granting access, delivering service), call /orders/complete to mark it done.

Cancelcanceled. Stop the order before completion via /orders/cancel. Handle refunds separately if payment already succeeded.

Expireexpired. Automatic after 7 days if payment never happens.

Payment status vs order status

Don't confuse payment status with order status—they track different things:

Payment status tells you about money:

  • requires_action – Customer needs to confirm or authorize
  • paid – Funds captured, money secured
  • failed – Payment attempt unsuccessful

Order status tells you about the order lifecycle:

  • requires_payment – Waiting for customer to pay
  • paid – Payment succeeded, ready for fulfillment
  • completed – Customer received items/service

Check order status to know where you are in the lifecycle. When order status is paid, ship the items. When fulfillment is done, call /orders/complete to mark it completed. The payment also has its own status that tracks the payment attempt itself.


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 /orders/new:

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 /orders/finalize or /orders/pay.

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 /orders/pay automatically finalizes the order if it isn't already sealed. The explicit /orders/finalize 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 /orders/pay, 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, generate a hosted invoice page where the customer selects and enters payment details. Retrieve the hosted page URL via /orders/hosted_invoice.

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 /orders/confirm_payment. 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 /orders/lookup 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 /orders/lookup 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 /orders/confirm_payment
  6. If token is correct, payment advances to authorize_payment next action
  7. Customer enters PIN in mobile money USSD prompt
  8. Poll /orders/lookup 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 /orders/request_confirmation.

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 /orders/invoice to force regeneration if needed.

Hosted invoice pages for payment

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

Retrieve the hosted page URL via /orders/hosted_invoice:

curl -X POST https://api.zebo.dev/orders/hosted_invoice \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"order_id": "order_xyz123"}'

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 hosted invoice 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:

POST /orders/new – Create a new order with line items and customer info. Optionally finalize and execute payment in one call.

POST /orders/lookup – Retrieve an order by ID. Returns current status, line items, payment state, and next actions.

POST /orders/finalize – Finalize an existing order. Makes line items immutable and generates invoice without executing payment.

POST /orders/pay – Execute payment for an order. Automatically finalizes if not already sealed. Initiates payment with attached method or generates hosted invoice.

POST /orders/confirm_payment – Submit customer confirmation token. Required when payment's next_action is confirm_payment.

POST /orders/request_confirmation – Manually request a new confirmation token. Use when customer didn't receive initial SMS or exhausted retry attempts.

POST /orders/hosted_invoice – Get hosted invoice page URL. Redirect customers to this URL for Commerce-hosted checkout.

POST /orders/invoice – Force invoice regeneration. Updates invoice to reflect current order state.

POST /orders/complete – 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.

POST /orders/cancel – Cancel an order before completion. Doesn't automatically refund successful payments—use refunds API separately.

POST /orders/page – List orders with pagination. Filter by status, customer, date range, or other criteria.

Was this page helpful?