Case Study

Configurable Commerce, Payments and Inventory Workflows

A commerce backend with explicit order stages and replaceable rules for pricing, payments and stock allocation.

This composable commerce case study covers a backend the client used to control how an order moves through checkout and fulfilment. We built that control around an order state machine. The rules for payment eligibility, basket merging and stock allocation sit behind it, and any one of them can be swapped out.

The same order begins life as a basket. It stays the working record through partial shipments, later changes and refunds. Storefronts talk to it over GraphQL, and staff manage the records from a dashboard.

The challenge

Moving an order to its next status can touch money or stock. Checkout has to validate the order before it enters payment. A change to a placed order may need a further payment, or a refund, to settle before the change is done.

Those checks belong at the transition itself. Put them anywhere else and a new storefront or extension could push the record forward without doing the work the step requires.

What we built

Adding an item recalculates the active order. A change of shipping or a new promotion does the same. Transition guards then check the order against its customer and its contents, and confirm that shipping and stock are in place. Payment handlers report whether a charge was authorised or settled, and whether it was declined or failed. A placement strategy sets the single moment the order counts as placed.

Staff can change a placed order and read its stage history. Plugins extend the services, the stored records and the API fields, and they can add screens to the dashboard. Strategy interfaces hold the business decisions that differ from one implementation to the next.

Check the transition, retain the evidence

When a transition is refused for an expected reason, it returns a named GraphQL result that the storefront can explain to the shopper. Split payments work from the order total less any payment already covered. Coupon limits are rechecked under a lock on the promotion row. Remove a coupon and the system returns the changed total for the shopper to deal with.

The provider call always runs first. It comes before the database transaction that records its result. A charge can go through while the following save fails, and that leaves a mismatch for staff to reconcile. A database transaction cannot wrap a network call to an outside provider, so the state machine keeps the two steps apart.

Stock sits in the system by location. An append-only ledger records every allocation and release, together with each sale and adjustment. Allocation strategies choose which locations supply an order.

The default payment-stage policy allows one awkward case. A stock shortfall can leave an order both paid and placed. The order emits an event, and the application has to handle it. Someone on staff has to respond, even though the order is already past its payment stage.

Project success

The client can change selected checkout rules through defined extension points and still trace an order's history. A named refusal explains why a transition stopped. Payment records and stock movements back the work that comes next. The order stays usable when a sale needs a change, or when an exception needs attention.

Technology stack

Application

TypeScript, Node.js, NestJS

API

GraphQL

Data access

TypeORM

Database options

PostgreSQL, MySQL, MariaDB, SQLite

Administration

React

Background jobs

SQL queues or BullMQ, separate worker process

Recognize the pattern?

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