Case Study

Multichannel Commerce and Order Management

A commerce API for channel-specific pricing, payment events and warehouse allocation.

This multichannel order management case study covers a commerce API where each selling channel sets its own terms. A channel picks its own prices and currency. Its warehouse preferences and order rules are also per channel. All storefronts share one GraphQL API, and behind it staff manage the draft orders and fulfilments. They also handle returns and refunds, and each action keeps an attached event history.

The payment, tax and shipping applications connect through manifests and through webhooks. Their provider-specific logic stays outside the commerce core.

The challenge

Payment can run ahead of order creation. A browser may close, a notification may repeat, or an allocation may fail after the charge is authorised. Through all of that, the backend keeps the checkout and payment facts while it works out what is left to do.

We also had to separate two ideas. Asking to capture money is not the same as learning that the provider captured it.

What we built

Channels control order confirmation, automatic checkout completion and warehouse allocation. Inventory then keeps warehouse stock, the reservations taken during checkout and the order allocations as three separate records. An allocation promises the units, while a fulfilment reduces the physical count.

Each payment transaction keeps an append-only event history. Its amounts come straight from those events. Sending a capture request does not by itself raise the charged amount, because the payment application still has to report the charge.

Technical approach

Each request brings an idempotency key. That key lets the backend recognise a repeat. To finish a checkout, the system locks it and loads it again. It then recalculates the totals, builds the order lines and allocates the stock. If an earlier request already did that work, the checkout token leads straight to the order it produced.

Stock rows are locked in a fixed order. A failed allocation or gift-card application releases the voucher usage tied to it. That stops a failed checkout from eating into the allowance.

Scheduled follow-up on authorised checkouts

Where automatic completion is turned on, a scheduled job picks up every checkout that qualifies. Channel delays and cut-off dates set that bar. Records that were tried less recently are brought to the front of the queue before the others.

A separate optional process releases funds for checkouts that never become orders. Automatic completion, fund release and the permission to fulfil unpaid orders are three independent settings. Operators line them up with each channel's payment policy.

Query-cost limits in GraphQL keep requests within bounds, and circuit breakers cap repeated calls to a failing integration. Even so, a synchronous checkout call still waits on its integration to respond.

Project success

Staff gain two clean distinctions here. One sets a temporary stock hold apart from an order allocation, and the other sets a requested charge apart from a confirmed one. An authorised checkout that never became an order still stays open for scheduled follow-up. From there, channel settings decide how the work proceeds, and one shared order and payment model holds across every storefront.

Technology stack

Application

Python, Django

Commerce API

GraphQL, Graphene

Database

PostgreSQL

Background processing

Celery

Queue and cache options

Redis, Valkey, SQS

Application server

Uvicorn

Recognize the pattern?

If one of these looks like your situation, the discovery call is where we find out how close.