Obiz Solutions

MannaHarvest Order Profit Tracker — Shopify → profit-per-order sync

Built for MannaHarvest.com, an e-commerce store that needed to know its actual profit per order, not just revenue. Orders sync automatically from Shopify, staff enter product and shipping costs through a small web app, and profit per order falls out of a database view — no spreadsheet, no manual calculation.

Architecture

Order Profit Tracker architecture: Shopify webhooks → Caddy → Fastify → PostgreSQL, with Fastify polling the Shopify Admin API on a schedule and staff reaching the web app over HTTPS

Three services in Docker Compose on a single VPS:

  1. Caddy — reverse proxy, automatic HTTPS.
  2. Fastify (Node.js) — the API and a small vanilla-JS frontend, no build step.
  3. PostgreSQL — orders, line items, and an order_financials view that cascades cost and revenue from line item → product → order → profit automatically.

Keeping orders in sync

Shopify webhooks (orders/create, orders/updated, orders/cancelled) push changes in real time, each verified via HMAC signature before being processed. Webhooks get missed sometimes — delivery failures, downtime — so a periodic reconciliation job re-scans recent orders every 4 hours as a fallback. Together they keep the full order history in sync: the webhook path handles the real-time case, reconciliation catches anything that slips through.

Fee sync: no webhook, so three layers of retry

Shopify doesn’t emit a webhook for payment-processing fees — that data only shows up once the charge settles on Shopify’s side, which doesn’t happen at the same moment the order is created. One lookup attempt isn’t enough, so fee sync is three layers deep:

  1. An immediate best-effort lookup fires the moment orders/create comes in.
  2. If the charge hasn’t settled yet, the order is flagged and a targeted retry job checks it hourly, up to five times.
  3. An unlimited, uncapped sweep runs every 6 hours as the fallback that never gives up on an order.

All three jobs also fire once on process startup, not only on their interval — setInterval timers reset on every deploy, so without that, redeploying during active development could leave orders fee-less far longer than intended. (A real bug, caught and fixed.)

Cost entry with carry-forward

Shopify doesn’t store real product costs for this store, so costs are entered by hand through an Order Detail view. To avoid re-typing the same cost on every order, once a cost is confirmed for a SKU it carries forward and pre-fills on the next order with that SKU — flagged for re-confirmation rather than silently trusted, since costs do change.

Security

Why this stack