Payday is an Icelandic invoicing and accounting platform, and this is its public Shopify app — listed on the Shopify App Store — that connects a merchant's store to their Payday account so store activity turns into correct invoices and accounting records without anyone touching a spreadsheet. From the outside it looks simple: connect a store, get invoices. Underneath, an order's life arrives as a stream of ambiguous, out-of-order webhooks — cancellations, refunds, returns, exchanges — and any one of them can create or alter a financial document, so the whole app is built around deciding when it is safe to act. It's live today with hundreds of merchants and a healthy monthly revenue; I rebuilt it alongside one other engineer and have been its sole developer since.
Payday Shopify App
A public Shopify app connecting merchants to Payday's Icelandic accounting platform, built to guarantee invoice correctness despite orders arriving as ambiguous, out-of-order webhooks. The visible product hides a decision system that untangles cancellations, refunds, returns and exchanges without ever writing a wrong financial record.

Payday
Live
Introduction
The Challenge
Payday needed a public Shopify app to connect merchants' stores to their accounting platform, where the core challenge is not simply syncing data but guaranteeing invoice correctness despite Shopify orders arriving as ambiguous, out-of-order webhook streams. Cancellations, refunds, returns and exchanges can all create or alter financial documents, and any misstep produces an accounting error in a real business's books rather than a cosmetic bug.
The Outcome
Live on the Shopify App Store serving hundreds of merchants with healthy monthly revenue.
Tech Stack
Frontend
- Next.js
- Apollo Client
- React
- Shopify Polaris
Framework
- Express
- Express.js
- Payday
Language
- TypeScript
- GraphQL
- Zod
- Node.js
Backend
- SQL Server
- Shopify Admin API
- @shopify/shopify-api
- jose
- Helmet
- express-rate-limit
- cron
CMS
- Shopify
Database
- Redis
- MongoDB
- ioredis
- Microsoft SQL Server
DevOps
- Docker
- Debian
- Azure Pipelines
- dependency-cruiser
- Azure Container Registry
- ngrok
- nodemon
Tooling
- tsx
- npm
- Next.js 15 (pages router) front end running behind a custom Express 5 server — deliberately a non-embedded Shopify app rather than one living inside the Shopify admin frame. Next.js handles the UI, middleware and GET-only API routes; Express owns webhooks, mutating routes and the long-running schedulers that need a persistent process.
- TypeScript throughout — roughly 52,000 lines across the client and server — with layered GraphQL query and fragment modules over the Shopify Admin API (orders, refunds, returns, products, transactions) so schema changes land in one place.
- @shopify/shopify-api for OAuth, HMAC verification and webhook registration, with Apollo Client and a hand-rolled fetch layer that handles GraphQL cost throttling.
- React 18 with Shopify Polaris for a UI that feels native to merchants even though the app lives outside the admin.
- Polyglot persistence: SQL Server for the core event log and shop/client records, MongoDB for third-party request/response logging, and Redis (via ioredis) for Shopify session storage.
- Zod v4 for runtime validation at trust boundaries, jose for JWE-backed session credentials, Helmet plus a custom nonce-based CSP middleware and express-rate-limit on the public surface.
- cron-driven schedulers for order requests, cancellation queues and original-invoice matching — work that can't be safely resolved on the webhook itself.
- Containerised with a hardened non-root Docker image (Node 22 on Debian slim, with MS ODBC drivers for SQL Server) and shipped through Azure Pipelines to Azure Container Registry.
- Dev loop uses ngrok tunnels with a scripted local setup so real Shopify webhooks hit a local server, plus tsx and nodemon for a typed, hot-reloading server.
Key Implementation Details
Order Lifecycle Correctness as an Architecture
- Dedicated webhook handlers cover orders, cancellations, direct cancellations, exchanges, refunds, asynchronous refunds and returns — each one able to create or amend a financial document.
- Behind them sits a decision layer that refuses to act until the state is genuinely unambiguous: original-invoice readiness checks, refund-coverage analysis, and an explicit invoice-matching policy.
- Work that can't be resolved on the webhook is handed to cron schedulers with explicit queue gates, so a return that depends on a not-yet-created invoice waits rather than guessing.
- A wrong invoice isn't a cosmetic bug — it's an accounting error in a real business's books, and that constraint shapes every branch in the codebase.
Untangling Shopify Exchanges — the hardest thing in the project
- Shopify emits no single "exchange" event. An exchange arrives as an interleaved tangle of return, refund and transaction webhooks that individually look like ordinary returns and refunds.
- A dedicated exchange-batching layer correlates those events and labels each part correctly, so the return webhooks don't trigger a return, the refund webhooks don't trigger a refund, and the added and removed items all land on one invoice with the right calculations.
- Shipped soon after Shopify first allowed exchanges in their admin, which meant building against a brand-new, sparsely documented event shape — and it's covered by its own targeted test suite.
Unambiguous Order Identity Across Reconnects
- Merchants reconnect, reinstall and change credentials, which historically allowed one Payday client identifier to be associated with more than one Shopify shop — meaning "did we already invoice this order?" could return another shop's row.
- The fix routes every lookup through a single resolver that resolves against the querying shop's own recorded identifier history and returns a classification (exact match, ambiguous match, recovered-history match, no match) rather than a boolean.
- Callers then interpret that classification for their own direction: duplicate prevention treats an ambiguous hit as a reason to stop, while the cancellation credit gate refuses to act on one. Same resolver, opposite polarity — the kind of distinction that only becomes obvious after a near-miss.
- Delivered as staged workstreams with an atomic, DB-backed claim to prevent races, and grandfathering so existing merchants keep working.
Tests Pointed at the Dangerous Parts
- Rather than chase coverage numbers, the suite concentrates on where an error becomes a wrong invoice: cancellation decisions, exchange batching, refund coverage, original-invoice matching, webhook snapshots and resource-ID parsing.
- Runs on Node's built-in test runner via tsx with no heavyweight framework, split into focused npm scripts (order-race fixes, refund/cancellation, branch matrices) so a risky change can be verified in seconds.
Living With Sustained Shopify API Churn
- Shopify's API and feature set move constantly, so keeping current is structural work, not occasional maintenance: versioned GraphQL fragments, a schema-validation script that checks queries against a pinned Admin schema dump, and centralised transformers between Shopify shapes and Payday's invoice model.
- dependency-cruiser enforces architectural boundaries (with a separate "critical" ruleset), and generated project-reference maps of exports, dependencies and structure keep a 52k-line codebase navigable for a single maintainer.
- Design decisions and deferred work are captured as dated design docs and an engineering backlog with explicit exit criteria and abort gates.
Public App Store Compliance
- Full mandatory GDPR webhook coverage — customer data requests, customer redaction and shop redaction — plus app install/uninstall handling, as required of a public Shopify app.
- Billing runs through Shopify's subscription APIs with plan checks and trial handling, alongside merchant-facing onboarding, settings, order and product surfaces (including bulk sync with confirmation steps and inventory options).
What I Contributed
- Rebuilt the app alongside one other engineer and became its sole developer, authoring roughly 1,325 of ~1,800 commits and maintaining it since.
- Built the exchange-handling layer that untangles Shopify's interleaved return, refund and transaction webhooks into a single correct invoice, shipped soon after Shopify first allowed exchanges in their admin.
- Designed and delivered the order-identity resolver that prevents cross-shop invoice duplication by classifying matches against a shop's own credential history and letting callers interpret the result for their direction.
- Architected the webhook decision layer, schedulers and queue gates that refuse to act until the state is genuinely unambiguous, backed by tests concentrated on cancellation decisions, refund coverage and invoice matching.
Outcome
-
52,000 lines of TypeScript across client and server, with layered GraphQL modules, hardened dependency boundaries and a test suite pointed at where errors become wrong invoices.
-
Full public-app compliance including GDPR webhooks, Shopify subscription billing and a decision system that survives sustained API churn without writing incorrect financial records.
A live Shopify App Store listing serving hundreds of merchants with healthy monthly revenue, where the visible product — connect a store, get correct invoices — hides a decision system built specifically to survive out-of-order, ambiguous webhooks without ever writing a wrong financial record. Every new operation the app learned to handle, from asynchronous refunds to exchanges, was its own substantial piece of engineering; the real achievement is the accumulated correctness of the whole. The codebase is structured, documented and boundary-enforced well enough that one developer can keep extending it against a platform API that never stops moving.