Skip to main content
← All project stories

Project 03 / Demonstration build

Two stores and a wholesale channel, one source of truth

Two Shopify stores and Stripe wholesale invoices send events to one Python service. Postgres records every order once under one customer. n8n sends shipped and delay notices and team alerts.

Category: E-commerce · Designed outcomes, not measured client results.

Shopify x2StripeOne order tableCustomer notices

Who this is for

Merchants running more than one Shopify store, or a store plus a wholesale channel, who reconcile orders in a spreadsheet.

The problem

Example client brief

“We have two Shopify stores. Orders, refunds, and fulfillment updates live in two admin panels. Someone copies everything into a sheet so we can see the whole business. Tracking numbers get missed and customers email asking where their order is.”
Three steps
  1. Order placed on either store, or a wholesale invoice paid
  2. Verified, matched to one customer, recorded once
  3. Shipped or late, the customer hears about it and the team is told

Designed outcome

Orders from every channel land in one place and nothing is entered twice.

The demo business

Loblolly, a small candle maker with two Shopify stores (Loblolly Candle Co and Loblolly Home) and wholesale accounts: boutiques that buy by the case and pay Stripe invoices. The company is invented. The stores, the Stripe account, the server, and the emails are real.

What the workflow does

  1. Both Shopify stores send order, refund, and fulfillment webhooks to one endpoint. Each store's signature is checked against its own key before anything is stored. A forged webhook is rejected with a 401 and logged with where it came from.
  2. Paid Stripe invoices come in as wholesale orders, with signature checks, and refunds are matched back to the right invoice.
  3. A Postgres database holds orders, line items, payments, refunds, fulfillments, and customers from all three channels in one schema. The same buyer is one customer record whichever channel they used.
  4. Every accepted webhook is recorded by its event ID, so a resent event changes nothing. Webhooks that arrive out of order, or before the order they refer to, are handled instead of dropped.
  5. A sweep finds paid orders that never shipped, alerts the team once, and sends the customer one delay notice.
  6. Shipped and delay emails and team alerts go out through n8n, from an outbox that holds them and retries until n8n takes them.

Tested on live accounts

Before recording, one buyer placed an order on each store and paid a wholesale invoice. All three landed as one customer with three orders, even with the email typed in a different case on the second store. Shopify sent the order update before the order itself, and both applied to one row. A resent Stripe event was ignored, a partial refund found its order, an unshipped order was flagged once, and a shipped notice sent while n8n was offline was delivered when it came back. A forged webhook got a 401.

Why Stripe is the wholesale channel

Shopify development stores can only take test payments through Shopify's own gateway, so Stripe is not bolted onto the stores. It plays the part it often plays in real businesses: invoicing for wholesale buyers who never touch the storefront.

Failure handling

When it breaks

01

Stripe resends a payment event.

It is recognized by its event ID and ignored. One payment record, not two, and the log shows the replay.

02

A paid order never ships.

The sweep marks it stuck, alerts the team in Slack, and tells the customer it is running late, once. A second sweep sends nothing new.

03

The notification service is down when an order ships.

The email waits in the outbox and goes out when the service is back. Nothing is silently lost.