Skip to content
Retail & Ecommerce Built by us

Ecommerce Operations Platform

The back half of an online store — orders, inventory, fulfillment and courier integration — for retailers whose operations have outgrown their platform.

The operations board. Orders grouped by state, exceptions surfaced at the top — this is the screen a fulfillment team lives in, and most platforms do not have one.

Who it's for

Built for a specific kind of business

Retailers and brands doing enough volume that fulfillment has become the bottleneck rather than traffic. Typically a business already selling online — often on Shopify or WooCommerce — where the storefront is fine but the operations behind it are held together by exports, manual stock adjustments and a courier portal in another tab.

Business type
Retailer, D2C brand, distributor selling online
Volume
500+ orders/month, or multi-channel
Trigger
Fulfillment errors, stock drift, platform fees, or an integration the platform will not support
Runs today on
Shopify/WooCommerce plus spreadsheets and a courier portal
Decision maker
Operations head, ecommerce manager, or owner

The problem

What this actually costs you today

In the words operators use, not the words software vendors use.

  • Inventory on the site and inventory in the warehouse disagree

    Stock is adjusted in two places by two teams. Oversells get discovered at picking, which means an apology, a refund, and a customer who does not come back. The gap widens with every channel you add.

  • Platform fees scale with revenue while your margin does not

    Transaction fees and app subscriptions are a rounding error at low volume and a genuine line item at scale. At some point the annual spend exceeds what building the operational layer would have cost.

  • Local payment and delivery integrations are unsupported

    Cash on delivery with partial prepayment, local wallets, regional courier APIs with their own status vocabularies — the integrations that matter most in your market are the ones a global platform is least likely to support.

  • Fulfillment lives in a spreadsheet and a courier portal

    Orders are exported, sorted, entered into a courier's website by hand, and tracking numbers are pasted back. It works, it is slow, and every step is a place for an order to fall out of the process silently.

  • Custom pricing and promotion rules are impossible to express

    Tiered wholesale pricing, customer-specific rates, bundle logic, region-based pricing — all straightforward in principle, all fighting the platform's data model in practice.

What it does

Grouped by who uses it

Feature lists organised by technical module tell you nothing about whether the software fits your team. These are grouped by the person who lives in them.

Catalog and variants

Merchandiser

A product model that handles the awkward cases — bundles, kits and variant matrices — rather than forcing them into a flat SKU list.

  • Variant matrices across size, colour and any other axis
  • Bundles and kits whose stock derives from their components
  • Per-channel pricing and availability
  • Bulk edit and CSV import for large catalogs
  • Media management with automatic derivative generation
  • SEO fields, structured data and canonical handling per product

Cart, checkout and payment

Customer

Built to convert on a mid-range Android phone over mobile data, because that is what most of the traffic is.

  • Guest checkout, with account creation optional after purchase
  • Address capture tuned for markets without reliable postcodes
  • Multiple payment methods including cash on delivery and partial prepayment
  • Local wallet and card gateway integration
  • Idempotent payment handling — no double charges on a retried submit
  • Abandoned-cart capture and recovery messaging

Order management

Operations

The centrepiece. Orders as a state machine with explicit transitions, not a status column somebody edits.

  • Order states from placed through to delivered, returned or cancelled
  • Partial fulfillment and split shipments
  • Order editing before dispatch, with a full change history
  • Cancellation and refund workflow with approval thresholds
  • Exception queue for orders stuck in any state past its SLA
  • Bulk actions across filtered order sets

Inventory and stock control

Warehouse

One source of truth for stock, with reservations that make overselling structurally difficult rather than merely unlikely.

  • Multi-location stock with per-location availability
  • Reservation on order placement, released on cancellation or expiry
  • Available-to-promise separate from physical on-hand
  • Stock movements as an append-only ledger, never a mutated quantity
  • Cycle counting and reconciliation workflow with variance reporting
  • Low-stock and reorder-point alerts
  • Purchase orders and goods-receipt against them

Fulfillment workflow

Warehouse

The physical process, modelled properly — pick, pack, dispatch — because that is where errors actually happen.

  • Pick lists batched and sequenced by warehouse location
  • Barcode scanning at pick and pack to catch errors before dispatch
  • Packing slips, labels and invoices generated together
  • Dispatch confirmation that triggers the courier booking
  • Returns processing with condition grading and restock decision

Courier and logistics integration

Operations

Where most of the operational time goes today, and where most of it can be removed.

  • Direct API booking with regional and international couriers
  • Automatic waybill and label generation
  • Rate shopping across couriers by weight, destination and service level
  • Tracking status pulled back and normalised into one vocabulary
  • Cash-on-delivery reconciliation against courier remittances
  • Failed-delivery and RTO handling as a first-class flow

Customer accounts and service

Support

Enough customer-facing surface to remove the most common support contacts.

  • Order history and live tracking without contacting support
  • Self-service returns initiation within policy
  • Saved addresses and payment preferences
  • Support view showing the full order timeline including courier events

Promotions and pricing rules

Merchandiser

Pricing logic that would need an app or a workaround on a hosted platform.

  • Tiered and customer-specific wholesale pricing
  • Percentage, fixed, BOGO and bundle discounts
  • Coupon codes with usage limits and eligibility rules
  • Region and channel-specific pricing
  • Scheduled campaigns with automatic start and end

Reporting

Owner

Operational reporting rather than marketing analytics — the numbers that tell you whether the back half is working.

  • Sales by product, channel, region and period
  • Fulfillment cycle time from order to dispatch
  • Courier performance and delivery success rate
  • Return rate by product and by reason
  • Stock turn, ageing inventory and dead stock
  • Cash-on-delivery collection and outstanding remittance

Screenshots

The actual interface

Every screenshot uses generated demo data. No real customer, student or member record appears anywhere on this site.

  • The stock ledger for one SKU. Every movement is an entry with a source document — which is why the on-hand figure can always be explained.

  • A batched pick list sequenced by bin location. Scanning at pick is what stops the wrong variant reaching the packing bench.

  • Rate shopping at dispatch. On high-volume routes this alone tends to pay for a meaningful share of the build.

  • COD reconciliation. In markets where cash on delivery dominates, this screen is frequently the single strongest reason to build rather than buy.

  • The exception queue. Orders that stalled, grouped by why — this is the difference between finding problems and being told about them by customers.

Roles & permissions

Who can do what

Publishing this table is unusual. It is also the fastest way to show that the system was designed for a real deployment rather than for a demo.

Warehouse staff, support agents and finance need genuinely different views of the same order. Splitting those roles properly is what keeps stock adjustments and refunds controlled.

Capabilities by role. Each row is a capability; each column is a role.
Capability Admin Full configuration Operations Manager All orders Warehouse Staff Assigned location Support Agent Customer-facing Finance Money and reconciliation
View orders Admin: Full Operations Manager: Full Warehouse Staff: View Support Agent: View Finance: View
Edit an order before dispatch Admin: Full Operations Manager: Edit Warehouse Staff: Support Agent: Edit Finance:
Cancel an order Admin: Full Operations Manager: Approve Warehouse Staff: Support Agent: Edit Finance:
Issue a refund Admin: Approve Operations Manager: Approve Warehouse Staff: Support Agent: Finance: Approve
Pick and pack Admin: Full Operations Manager: Edit Warehouse Staff: Full Support Agent: Finance:
Dispatch and book courier Admin: Full Operations Manager: Full Warehouse Staff: Edit Support Agent: Finance:
Adjust stock Admin: Full Operations Manager: Approve Warehouse Staff: Edit Support Agent: Finance:
Approve a stock variance Admin: Approve Operations Manager: Approve Warehouse Staff: Support Agent: Finance: View
Purchase orders and receiving Admin: Full Operations Manager: Full Warehouse Staff: Edit Support Agent: Finance: View
Catalog and pricing Admin: Full Operations Manager: Edit Warehouse Staff: Support Agent: Finance:
Promotions and coupons Admin: Full Operations Manager: Edit Warehouse Staff: Support Agent: Finance: View
COD reconciliation Admin: Full Operations Manager: View Warehouse Staff: Support Agent: Finance: Full
Customer personal data Admin: Full Operations Manager: View Warehouse Staff: Support Agent: Edit Finance: View
Financial reports Admin: Full Operations Manager: View Warehouse Staff: Support Agent: Finance: Full
Audit log Admin: View Operations Manager: View Warehouse Staff: Support Agent: Finance: View
  • Full Full access
  • Edit Create and edit
  • View View only
  • Approve Requires approval
  • No access
  • Enforced server-side, not by hiding UI

Technical architecture

How it is built

Skip this section if you are not technical — nothing later depends on it.

ASP.NET Core over PostgreSQL, with the storefront rendered separately from the operations application. Inventory and orders are both modelled as event ledgers rather than as mutable quantities and status columns — that is the decision that makes concurrency and auditability tractable at the same time.

  1. Clients

    • StorefrontServer-rendered, on a CDN
    • Operations consoleReact, keyboard + scanner first
    • Warehouse scannersPick and pack confirmation
  2. Application

    • ASP.NET Core APIREST + OpenAPI
    • Order state machineTransitions fire side effects
    • Reservation serviceRow-locked availability check
    • Payment intentsIdempotency key per attempt
  3. Integration adapters

    • Courier adaptersOne interface, many providers
    • Payment gatewaysCards, wallets, COD
    • Storefront syncShopify / WooCommerce
  4. Data

    • PostgreSQLOrders, shipments, catalog
    • Stock movement ledgerAppend-only; balance is a cache
    • RedisAvailability cache + job queues
    • Search indexCatalog search
Stock is a ledger, not a number. Reservations are written inside the same transaction that checks availability, so two orders for the last unit serialise instead of both succeeding.
Stack, layer by layer
  • Operations API ASP.NET Core 8 (C#)

    Money, stock and state machines. Transactional correctness is the entire job.

  • Storefront Astro or Next.js, server-rendered, on a CDN

    Conversion is bound by load time on mobile data. Static-first rendering wins that.

  • Database PostgreSQL 16 with row-level locking and advisory locks

    The inventory reservation problem is solved by the database's concurrency primitives, not by application-level guesswork.

  • Cache and queue Redis

    Availability lookups on hot SKUs, plus queues for courier booking and webhook delivery.

  • Search Meilisearch or Elasticsearch

    Catalog search is the highest-intent action on a storefront and deserves a real index.

  • Ops UI React 19 + TypeScript, keyboard-first, barcode-scanner friendly

    Warehouse staff use scanners and keyboards, not mice. The UI is built for that.

  • Integrations Per-courier adapters behind one internal interface

    Courier APIs are wildly inconsistent. Normalising them once protects the rest of the system.

Data model highlights
  • StockMovement as an append-only ledger; on-hand is the sum, never a stored mutable number
  • Reservation with an expiry, so an abandoned checkout returns stock automatically
  • Order with an explicit state plus an OrderTransition history
  • OrderLine allocated against reservations, so partial fulfillment is representable
  • PaymentIntent and PaymentAttempt separated, with an idempotency key on every attempt
  • Shipment as a distinct aggregate — one order can produce several
  • CourierEvent normalised from each provider's vocabulary into one internal status set
  • CodRemittance matched against shipments for reconciliation
Integrations
  • Regional couriers — API booking, label generation, tracking and COD remittance
  • Payment gateways and local wallets
  • Shopify or WooCommerce, where the storefront stays and only operations are replaced
  • Accounting systems for invoice and settlement posting
  • WhatsApp and SMS for order and delivery notifications
Deployment topology

Containerised in your cloud account, with the storefront on a CDN and the operations application behind authentication. Can be deployed as a full replacement or as an operations layer sitting behind an existing Shopify or WooCommerce storefront.

Notable engineering decisions

The trade-offs we made, and why

This is the section that separates a system somebody built from a system somebody screenshotted.

Inventory as a ledger, not a number

The single most common defect in ecommerce systems is a mutable quantity_on_hand column. It is fast, obvious, and wrong in two distinct ways.

The first is concurrency: read-modify-write on a shared counter loses updates under load, which is exactly when it matters. The second is worse — when the number is wrong, and it eventually is, there is no way to find out why. You cannot audit a number that only holds its current value.

Every stock change is instead an immutable movement: received, reserved, picked, shipped, returned, adjusted, each with a quantity, a location, a timestamp and a source document. On-hand is the sum of movements for a SKU at a location.

The obvious objection is performance, and it is a real one — summing a ledger on every product page does not scale. The resolution is a materialised balance maintained inside the same transaction as the movement, treated as a cache of the ledger rather than as the truth. A reconciliation job recomputes balances from the ledger periodically and alerts on any drift. In practice drift means a bug, and having a mechanism that detects it is worth considerably more than the small cost of maintaining both.

What this buys operationally: when a warehouse manager asks why the system says 14 and the shelf says 12, the answer is a list of movements with source documents attached, not a shrug.

Reservations, and the overselling problem

Availability is not on-hand. The number that matters at checkout is available to promise — on-hand, minus reserved, plus inbound that is committed.

When an order is placed, a reservation row is written inside the same database transaction that checks availability, taking a row-level lock on the stock line for that SKU and location. Two concurrent orders for the last unit serialise: one commits, the other fails its availability check and is told at checkout that the item has gone. That is a worse customer experience than a successful order and a far better one than an apology email two days later.

Reservations carry an expiry. An abandoned checkout releases its stock automatically rather than holding it until someone notices. Confirmed payment converts the reservation into an allocation; dispatch converts the allocation into a shipped movement.

The lock is deliberately narrow — one stock line, held for the duration of a short transaction. Locking a whole product or, worse, using an application-level mutex, is how checkout throughput dies on a flash sale.

The order state machine

Orders acquire state transitions the way software acquires features — one special case at a time — until nobody can say with confidence what states exist. We define them up front and enforce them:

Placed → Confirmed → Allocated → Picking → Packed → Dispatched → Delivered
   ↓          ↓           ↓                              ↓
Cancelled  Cancelled  Cancelled                    Returned → Refunded

Each transition names who may perform it, what must be true beforehand, and what side effects it triggers. Dispatched books the courier and sends the customer notification. Cancelled releases reservations. Returned creates an inbound movement pending a condition grade.

Two properties matter more than the diagram itself. Transitions are recorded as rows, so an order’s history is complete and an exception queue can find everything that has been in Picking for more than four hours. And side effects fire from the transition rather than from the endpoint that caused it, so an order cancelled by a customer, by support, or by a payment timeout all release stock identically — instead of two of the three paths remembering to.

Payment idempotency

Payments fail in the least convenient ways: the gateway charges the card and the response times out; the customer taps submit twice on a slow connection; a webhook is delivered three times because the first two acknowledgements were lost.

Every payment attempt carries a client-generated idempotency key, stored before the gateway is called. A retry with the same key returns the original result rather than initiating a second charge. Gateway webhooks are deduplicated on the provider’s event ID and processed in a transaction that is safe to replay.

The distinction that does the work is separating PaymentIntent — the customer’s intention to pay this amount for this order — from PaymentAttempt, of which there may be several. It makes the double-charge case representable and therefore preventable, rather than a race condition that shows up as a support ticket.

Courier integrations: one interface, many adapters

Courier APIs are the least standardised systems most ecommerce operations touch. One returns XML. Another requires a session token refreshed hourly. Their status vocabularies overlap without agreeing — “out for delivery”, “in transit”, and “at delivery hub” mean different things to different providers, and a couple of them will report a delivery before the parcel has moved.

Every courier sits behind one internal interface — book, label, track, cancel — and each adapter is responsible for translating that provider’s vocabulary into a single internal status set. Nothing above the adapter layer knows which courier a shipment is on.

This makes rate shopping possible at all, and it means adding a courier is one adapter and one set of tests rather than a change that ripples through the order module. It also contains the damage when a provider changes their API without notice, which in our experience they do.

The unglamorous part is status normalisation, and it is where the time actually goes. Getting it right is what lets the customer-facing tracking page and the internal exception queue read from the same data — instead of the support team learning the real status from the courier’s own website.

Demo access and customisation

See it running

A seeded demo covering the order-to-dispatch flow is available during a call. All catalog, customer and order data in it is generated.

Deploy as-is, or fit it to your process

As-is
6–8 weeks
Customised
12–20 weeks

Courier and payment integrations are the variable. The order, inventory and fulfillment core transfers directly; each courier or gateway adapter is roughly a week depending on how good their documentation is — and it usually is not.

FAQ

Ecommerce Operations — questions we get asked

  • Should we replace Shopify entirely?

    Usually not, and this is the most common conversation we have on ecommerce projects. Shopify is a very good storefront and checkout. What it is not good at is multi-location inventory, warehouse workflow, regional courier integration and COD reconciliation. The most common engagement is to leave the storefront where it is and build the operational layer behind it, syncing orders and stock. That is cheaper, lower risk, and keeps the part that already works.

  • How do you prevent overselling?

    Stock is a ledger, not a number, and placing an order writes a reservation inside the same transaction that validates availability, using row-level locking on the stock line. Two simultaneous orders for the last unit cannot both succeed — one commits and the other is told the item is gone, at checkout, rather than at picking. Reservations expire, so abandoned carts return stock automatically.

  • Can it handle cash on delivery properly?

    Yes, and this is one of the strongest reasons to build rather than buy in South Asian and Gulf markets. COD is modelled end to end — partial prepayment, collection at delivery, courier remittance matching, RTO handling, and the reconciliation report that tells you which courier is holding your money.

  • What about the storefront design?

    We are not the right people for a design-led storefront rebuild, and we will say so. Our value is in the operations behind it. If the project is primarily about how the store looks, hire a design studio; if it is about what happens after the order is placed, that is the work we do well.

  • How long before it pays for itself?

    The honest answer is that it depends on where your losses currently are. Rate shopping across couriers, eliminating oversells, and removing manual courier data entry are the three that usually dominate, and all three are measurable before you commit. During discovery we quantify them against your actual order volume rather than quoting an industry figure.

Book a walkthrough of Ecommerce Operations.

We will show you the working system, tell you honestly what would need changing for your operation, and give you a range before you commit to anything.

30 minutes · We reply within one business day.

WhatsApp Book a call