Prototype · September 2026

Local Distribution Dispatch: from a Shopify checkout to a delivered order, with SAP in between.

A working prototype of the distribution layer a business needs once it sells online but delivers through independent local distributors. Web orders from Shopify become sales orders in SAP S/4HANA, are dispatched to the right distributor, and are closed by drivers on a phone app with the customer’s PIN, a returnables count and a photo. The demo runs a propane cylinder business in Dallas–Fort Worth. I built it to show how I would design the system; the data is fictional and the Shopify and SAP calls are simulated.

The dispatch app: today's figures above a live map of Dallas–Fort Worth with vehicles on their routes and a floating legend, and the driver's phone beside it
The dispatch console, with the driver’s phone beside it. Every screen on this page is the running prototype, not a mock-up.

The gap

Two good systems and a phone call between them.

A business that adds a web shop but delivers through local distributors usually ends up with Shopify taking the order and the payment, and SAP holding the customer, the material, the price and the stock. What happens between the two is often handled on WhatsApp and in spreadsheets: which distributor takes the order, which driver goes, whether it arrived, whether the returnables came back, whether the cash reached the bank.

The customer’s order page says “processing” until someone remembers to update it. Management sees orders and invoices but not the delivery in between. This prototype is that missing layer, and it does not care what the product is.

Illustration of the journey: an online checkout, the ERP, a dispatch desk, a distributor's warehouse, a delivery van and a customer's door, with return lines from the door back to dispatch and the ERP
The journey the prototype tracks, from checkout to the customer’s door and back. Illustration generated with ChatGPT from my description of the flow.

Track record

The system is a prototype. Its parts are not.

I have not shipped this exact system for a client, and this page does not pretend otherwise. Every hard part of it, though, is something I have delivered in production, most of it for clients abroad.

  • Vehicles and drivers in the field

    Autom8

    Client · live

    GPS fleet telematics with live vehicle video: device protocol servers on TCP and UDP, HLS transcoding through FFmpeg, and a deployment runbook onto the client’s own server.

    autom8.kakushin.io
  • Integration with an enterprise system of record

    National electricity utility apps

    Europe · regulated · under subcontract

    Native iOS and Android customer apps over eight backend services, integrated through Azure API Management onto the utility’s billing platform (Oracle CC&B). Coordinated seven external counterparties and drove penetration-test remediation to production acceptance.

  • Many independent operators on one platform

    BookMi

    Client · multi-vendor marketplace

    Venues, packages and transport from independent vendors, with escrow and vendor payouts. Production-freeze governance on payment flows and a 233-test Playwright suite against 148 documented use cases.

    bookmi.kakushin.io
  • E-commerce tied to statutory invoicing

    Monvasi

    Client · Greece · delivered

    B2C olive-oil marketplace with myDATA invoicing, so every web order produces the tax document the authority requires: the same order-to-finance discipline as posting a web order into an ERP.

  • Mobile apps that work without signal

    oajax.js

    Open source · author · 2016 · MIT

    Offline-first request library written for field apps: requests made with no connectivity are held on the device and replayed when it returns. The driver app’s sync model is this idea, a decade on.

    github.com
  • Five years building for US companies

    PAO · RYZA · SwotKnot

    Boulder, CO and Palo Alto, CA · 2018 – 2023

    Backend services, deployments and releases for a local-discovery marketplace on the App Store (PAO); full architecture of a real-time co-creation platform (RYZA); lead developer of a learning platform (SwotKnot).

The flow

Eight steps, each one recorded.

Order flow from Shopify through SAP to a distributor’s driver and backA Shopify order is verified by the integration service, posted to SAP as a sales order, and passed to the dispatch core, which assigns it to a partner distributor; the partner’s driver delivers it and captures the customer’s PIN, returns and a photo. Proof of delivery flows back to dispatch, which posts goods issue and billing to SAP and marks the Shopify order fulfilled.orders/createHMAC-verifiedsales orderODataPIN to customerorder + SO no.rank · assignjobload · deliverPIN · returns · photo · cashgoods issue · billingfulfilmentKPIsfailed attempt → reassignShopify storecheckout · My ordersIntegration servicequeue · idempotency · retriesSAP S/4HANABP · sales order · deliverySMS gatewaydelivery PIN · ETAReportingSAP Analytics Cloud / Power BIDispatch corestate machine · ranking · auditPartner portalindependent distributorsDriver appaccept · scan · deliverCustomer’s doorPIN · returns · photo12345678order out to the fieldproof and money back to SAP and Shopify
  1. 01

    Checkout

    The customer orders on the Shopify store. The orders/create webhook is verified (HMAC-SHA256) and stored idempotently by webhook ID before anything else happens; the customer is texted a four-digit delivery PIN.

  2. 02

    Sales order

    The order is posted to SAP S/4HANA as a sales order. A first-time customer is created as a business partner first; if SAP rejects the post, the order is held as an exception rather than silently lost.

  3. 03

    Dispatch

    Partners are ranked for each order: zone coverage is a hard rule, then stock at their yard, then their 30-day on-time record, then open load. Dispatch can auto-assign the queue or override any single order.

  4. 04

    Accept

    The partner’s driver sees the job on the phone app, and only their own jobs, with the customer’s number masked.

  5. 05

    Load

    Each line is scanned at the yard. The yard’s stock is drawn down, so a partner running short stops being offered orders it cannot fill.

  6. 06

    Deliver

    At the door the driver enters the customer’s PIN, counts any returnables handed back, records money collected and takes a photo. A wrong PIN keeps the job open.

  7. 07

    Post back

    Goods issue and billing are posted in SAP, the Shopify order is marked fulfilled, and the customer’s “My orders” page shows the delivery time.

  8. 08

    Exceptions

    A failed attempt returns to dispatch with its reason and is reassigned; late orders, SAP rejections and failures share one queue.

Management view

What the head of distribution asks on Monday morning.

Analytics screen: orders per day and on-time rate over fourteen days, then on-time rate, returnables held and open jobs for each dealer
Volume and punctuality over two weeks, then three questions per partner: are they on time, how loaded are they, and how many returnables are they sitting on. Every chart has the same numbers as a table.

Order desk

Not every order is a checkout. The phone takes the same road.

Call centre screen: an agent on a call with a recognised customer, the customer's record with address, order history and returnables, today's orders with the driver's status, and an order form
The call-centre screen in the demo. Calls are generated, not received; everything after the ring is the real flow.

The record opens before the hello

The phone system passes the caller’s number in as it rings. The agent sees who it is, where they live, their SAP partner number, how many returnables they hold, today’s orders and any notes, without asking.

One pipeline, two doors

A phoned-in order is written to Shopify as a draft order and completed, so it arrives by the same webhook as a web checkout and posts to SAP the same way. One catalogue, one price list, one audit trail.

The last mile reports back

“Where is my order” is answered from the driver’s live status. A failed delivery becomes a callback, the desk agrees a new time and the order returns to dispatch. A note such as a gate code travels to the driver’s phone.

Two phones

The driver does the paperwork at the door. The customer watches it happen.

Driver app: delivery screen asking for the customer's PIN, returnables collected, amount collected and a photo
Driver
Customer tracking page: arriving in about twelve minutes, progress bar, five plain steps, the delivery PIN and the driver's name
Customer

A driver sees their own jobs in window order: accept, scan the goods at the yard, deliver. The delivery screen will not close without the customer’s PIN, and it records the returnables handed back, the amount collected at the door and a photo of the delivery in place.

If the delivery cannot be made, the driver picks a reason and the order goes straight back to dispatch with it, instead of sitting in a van until the end of the shift.

The customer’s tracking page reads the same order record, so nobody updates them by hand: it shows the ETA, five plain steps with no system jargon, and the PIN the driver will ask for.

The driver’s phone never shows the customer’s full number, and a driver cannot see another driver’s jobs. The production app works offline and syncs when signal returns.

Integration

What actually crosses the wire.

The three calls that matter, as the production build would make them. Field names follow the Shopify Admin API and the S/4HANA OData services; organisation values and IDs are placeholders.

1 · Shopify → integration service
POST /webhooks/shopify/orders-create
X-Shopify-Topic: orders/create
X-Shopify-Webhook-Id: 7b1c9e2a-…          ← idempotency key
X-Shopify-Hmac-Sha256: 3q2+7w…            ← verified before parsing

{
  "id": 5821390233716,
  "name": "#10413",
  "financial_status": "pending",
  "payment_gateway_names": ["Cash on Delivery (COD)"],
  "total_price": "84.48",
  "currency": "USD",
  "shipping_address": {
    "address1": "2210 Cedar Springs Rd", "address2": "Apt 1804",
    "city": "Dallas", "province_code": "TX", "zip": "75201"
  },
  "line_items": [
    { "sku": "LPG-CYL-20X", "quantity": 2, "price": "24.99" },
    { "sku": "LPG-ACC-RHK", "quantity": 1, "price": "34.50" }
  ]
}
2 · Integration service → SAP sales order
POST /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder
x-csrf-token: <fetched first>

{
  "SalesOrderType": "OR",
  "SalesOrganization": "1710",
  "DistributionChannel": "10",
  "OrganizationDivision": "00",
  "SoldToParty": "17100042",
  "PurchaseOrderByCustomer": "#10413",
  "to_Item": [
    { "Material": "LPG-CYL-20X", "RequestedQuantity": "2", "RequestedQuantityUnit": "EA" },
    { "Material": "LPG-ACC-RHK", "RequestedQuantity": "1", "RequestedQuantityUnit": "EA" }
  ]
}

→ 201 Created   SalesOrder "812341"
7 · Proof of delivery → SAP goods issue → Shopify fulfilment
# when the driver's PIN is verified
POST /sap/opu/odata/sap/API_OUTBOUND_DELIVERY_SRV;v=0002/
     PostGoodsIssue?DeliveryDocument='80046210'

# then back to the store
POST /admin/api/2024-07/fulfillments.json
{
  "fulfillment": {
    "line_items_by_fulfillment_order": [{ "fulfillment_order_id": 6934817… }],
    "notify_customer": true
  }
}
ShopifySAPRule
namePurchaseOrderByCustomerShopify order number carried into SAP for reconciliation
customer / shipping_addressSoldToParty (business partner)Looked up by phone and email; created through API_BUSINESS_PARTNER if new
line_items[].skuto_Item[].MaterialShopify SKU is the SAP material number, so there is no mapping table to drift
line_items[].quantityRequestedQuantityUnit of measure fixed per material
payment_gateway_namesPayment termsCard, cash on delivery or on account, set on the sales order
X-Shopify-Webhook-IdIdempotency keyA replayed webhook finds its earlier result instead of creating a second order

Architecture

What a production build would run on.

Integration service
Node.js / TypeScript workers behind a queue (Azure Service Bus or equivalent). A webhook receiver writes to an outbox; workers call SAP over OData (API_BUSINESS_PARTNER, API_SALES_ORDER_SRV, API_OUTBOUND_DELIVERY_SRV) or through SAP Integration Suite where the landscape has standardised on it. Every call carries an idempotency key; failures retry with backoff and then land in a dead-letter queue that the console shows as exceptions.
Dispatch core
PostgreSQL with an explicit order state machine (new → assigned → accepted → loaded → delivered, or failed) and an append-only audit trail on every order. Catalogue, geography and partner network are configuration, which is how the same engine runs both scenarios.
Partner portal
The same console, scoped by role and tenant with row-level security: a distributor sees its own orders, drivers and stock, and nothing else.
Driver app
React Native, offline-first: actions queue on the phone and replay when signal returns, the same idea as oajax.js, the offline request library I published in 2016. GPS, camera and barcode scanning.
Reporting
Partner SLA, on-time rate, returnables outstanding and cash to reconcile, fed to the BI layer (SAP Analytics Cloud or Power BI) for management.
Identity and security
SSO with MFA for staff (Microsoft Entra ID), separate partner accounts, device-bound tokens for drivers, secrets in a vault, customer data masked by default.

Decisions

The choices I would defend in a design review.

  • SAP stays the system of record.

    Customer, material, price, stock and money live in SAP. Dispatch owns only the delivery workflow, so there is never a second price list to drift.

  • No dispatch without a sales order.

    Nothing leaves a yard without a sales order to post the goods issue against. An order SAP holds back shows up as an exception, not as a gap someone finds at month end.

  • The PIN is the proof of delivery.

    Not the driver’s word. It also protects the partner when a customer disputes a delivery.

  • Returnables are counted per order.

    An unreturned cylinder or crate is lost capital. Counting at the door is what lets the yard, the plant and SAP’s returnable-packaging balance agree.

  • Least privilege by default.

    Drivers see a masked phone number and only their own jobs; partners see only their own data.

  • Everything is idempotent.

    Shopify re-sends webhooks, mobile networks drop and SAP calls time out. A retry must never become a second sales order or a second goods issue.

Scope

What the demo does, and what it doesn’t.

Working in the demo

  • The full order flow, from a new Shopify order to SAP goods issue and Shopify fulfilment
  • Live mode: orders arrive, dispatch clears the queue and drivers deliver on their own, so the board can be watched as well as worked
  • A guided walkthrough that plays one order from checkout to SAP goods issue, hands off, in about forty seconds
  • A live map on Google Maps, styled dark, with vehicles moving to the door and an ETA for each
  • A call-centre screen: caller recognised by number, customer record with history, returnables and notes, orders taken by phone, callbacks for missed deliveries
  • The customer’s tracking page, reading the same order as dispatch and the driver, with the delivery PIN
  • An analytics screen: volume and on-time rate over two weeks, and on-time, load and returnables by partner
  • Refill and first-time orders with a refundable deposit; standard and express delivery windows
  • Partner ranking with reasons, auto-assign and manual override
  • A first-time customer rejected by SAP, then retried with a new business partner
  • A failed delivery returned to dispatch and reassigned
  • Yard stock, returnables and cash at the door reconciled as orders close
  • An audit trail per order and an integration log across Shopify, SAP, SMS and the phone system

Simulated or left out

  • Shopify and SAP calls are simulated in the browser, using the real endpoint names
  • No live GPS or route optimisation; vehicles move in straight lines across a real map
  • No telephony; incoming calls are generated in the browser
  • No sign-in; the partner and driver views are switchable for the demo
  • State is kept in the visitor’s browser, not on a server

Next

Try it, then ask me how I would build yours.