Case study 01Addex Group · 2025 – present

A procurement portal that enforces how a company actually spends

A permissions-driven purchasing platform for multi-site organisations: hierarchical budgets, multi-level approvals and company-specific catalogues and pricing, split across a branded buyer portal and an embedded Shopify Admin app running on one shared domain layer.

Role
Sole engineer
Owned
Architecture, data model, build, deployment
Stack
React · Remix · PostgreSQL · Prisma · Redis · Shopify Admin API
Buyers
High-value corporate accounts
Interface
Problem

Purchasing controls that existed on paper but not in the system

Corporate customers buying PPE and equipment needed structured purchasing across departments, teams and locations. Each organisation had its own rules: who could order, up to what value, against which budget, and who had to sign it off.

None of that existed in the storefront. Approvals ran on email, PDFs and hand-kept spreadsheets, so every order carried delay and the risk of falling outside policy. For accounts at this scale, that was the blocker to buying online at all.

Constraints
01
Shopify checkout could not be modified, so controls had to be enforced before cart handoff
02
Every customer organisation had a different hierarchy; the model had to be configurable, not coded per client
03
Buyers are not technical; the interface had to read like an internal tool, not a developer console
04
Single engineer, live accounts, no tolerance for incorrect pricing or unauthorised spend
System

Two interfaces, one set of rules

The buyer portal is a Remix application on its own subdomain. Shopify handles the sign-in, so buyers never have a second account to create or remember.

Merchant staff work in an embedded Shopify Admin app, configuring companies, users, catalogues, pricing and budgets. Both run on the same underlying rules, so pricing, permissions and approvals behave identically wherever an action starts.

Shopify stays the source of truth for products, checkout and orders. PostgreSQL owns organisations, entitlements, budgets, price lists and request state.

Layers
Buyer portalReact · Remix · own subdomain · branded per store
Merchant adminEmbedded Shopify Admin app · Polaris
AuthenticationShopify sign-in · portal session
Shared rulesPermissions · budgets · catalogue · requests
PricingCompany price lists · pack sizes · NetSuite sync
DataPostgreSQL · Prisma · one database per store
CommerceAdmin API · orders · draft orders · webhooks
Decisions

Six decisions that shaped the build

01

Budgets as a structure, not a field

A flat spend limit per user cannot express a company budget. The model carries individual allowances, shared pools and unlimited authority, so allocation cascades from organisation to person.

02

Company pricing, kept in step

Each company sees its own price list, kept in step with NetSuite rather than maintained by hand. Pack sizes sit alongside it, so buying in quantity is part of the catalogue rather than a code applied at the end.

03

What a buyer sees, kept apart from what they may buy

The purchasing catalogue combines Shopify product data with company price lists, pack sizes and bundles, deliberately apart from collections and navigation. Merchandising the storefront can never quietly change what a company may order, or the price it pays.

04

Purchase requests, not pending carts

Orders begin as internal requests with their own permanent number and history. A request may need several approvals, be rejected, become an order outright, or become a draft order awaiting payment.

05

One sign-in, one identity

Buyers sign in once, through Shopify. The portal works from that identity rather than keeping its own copy of the customer, so there is one account per person and nothing to keep in step.

06

Two systems that cannot share a transaction

Shopify and the portal’s database cannot be updated as a single step, so the order flow is built to survive either one failing: locking, safe retries and reconciliation afterwards. That is what stops two people spending the same budget, or one order being placed twice.

InterfaceFig. 02 – 04
The screens that carry the architecture
Results

Policy moved out of spreadsheets and into the platform

Approvals that previously ran on email and manual checks are now enforced by the system, with spend visible against budget at the moment of ordering rather than in a monthly reconciliation.

The permissions, budget and approval model has since become the foundation other B2B automation is built on, rather than a one-off feature for a single account.

Measured
High-value corporate accounts now purchasing through the portal
Approval chains enforced by the platform rather than by email and spreadsheet
Orders processed through Shopify, so fulfilment and finance needed no new integration
Company setup, catalogues, pricing and reporting owned by merchant staff
Judgement

Trade-offs

I chose not to build the portal fully outside Shopify. Accounts, authentication and order processing stay on the platform, so the operational systems behind the business were already connected: no parallel identity store, no new fulfilment APIs, no second source of truth for orders.

The cost is a hard ceiling: checkout cannot be modified, rate limits shape how writes are sequenced, and part of the data model exists to mirror concepts Shopify has no notion of.

What I would do differently

The first version lived inside the Shopify theme, because the brief was to keep the experience close to the storefront. The theme was the wrong host for an application of this size.

Rebuilding it as a Remix application on its own subdomain made development and the portal itself dramatically faster, with a clear route back to the store so buyers never feel detached. I would start there next time rather than arrive by migration.

Selected workAll selected workNext case study · 02The operational layer Shopify leaves to youLeach’s storefront, one of four brands on the platformLeach’s