Skip to content

Welcome — what MerchOS is

The business

YourCustomMerch.ca is a Canadian custom-merchandise business serving companies across nine active verticals. Founded and run by two sisters — Jess and Joy. The business has two products:

ProductWhat it isStatus
Marketing sitePublic-facing site at yourcustommerch.ca (Astro, separate repo)Live on Vercel
MerchOS PlatformPrivate operational platform for running the businessLive, gated behind pre-launch password until launch day

MerchOS is YCM’s internal operating system. YCM’s client companies pay YCM per a pricing model (see below) for a branded merch storefront and get charged margin on every order. MerchOS powers it all internally — it is not a multi-tenant SaaS product YCM sells to other merch businesses.

If you internalize one sentence about the project: MerchOS is the operating system for one specific custom-merch business, not a SaaS platform for many. That single fact rules out a lot of architectural temptations (per-tenant white-labeling, dynamic pricing-tier configuration, generic supplier abstraction) that would otherwise be obvious moves.

The five user roles

RoleInterfaceDescription
Super AdminInternal Admin (internal.yourcustommerch.ca)YCM team — full platform access
Internal DesignerInternal Admin → Design QueueReviews print-file revisions
Company AdminCompany Admin Portal (admin.yourcustommerch.ca)HR/ops manager at a client company
Company ManagerCompany Admin Portal (limited)Team lead with partial admin access
EmployeeStorefront (store.yourcustommerch.ca/[slug])Orders merch (gated companies only)

For ungated stores (Travel & Tourism, Events, etc.), shoppers are anonymous — no employee account, no login, pay via Stripe Checkout. That mode is not a fifth role; it’s the absence of one, and it changes a lot about how those storefronts are built.

The nine active verticals

Canonical source: prisma/migrations/20260425_seed_canonical_verticals_and_suppliers/migration.sql (the original eight) plus prisma/migrations/20260511_phase7a_departments_and_member_pays/migration.sql (Departments, the ninth). When you need an authoritative list, query the Vertical table or read those migrations — never trust prose elsewhere in the codebase.

The nine fall into three pricing-model families:

MONTHLY_SUBSCRIPTION (gated B2B)

Employees log into a private storefront with invites, spend limits, and company-funded orders. Three verticals:

  • businesses-corporate
  • trades-contractors
  • creators-collectives

This is the original YCM product. Tier ladder (STARTER / GROWTH / PRO / ENTERPRISE) charges monthly; orders are invoiced to the company at end of month.

Per-company split-tender (ADR-0104, Phase 0141 — shipped 2026-05-19). When an employee’s cart exceeds their remaining allowance, they can opt to pay the overage themselves with their own card via Stripe Elements at checkout. Company credit covers up to the limit, employee tops up the rest — one cart, one order, one fulfillment, split payment. Per-Company toggle on Company.allowEmployeeTopUp; vertical defaults are default-on for trades-contractors, default-off for businesses-corporate + creators-collectives. The spend-limit invariant (CLAUDE.md §6) is preserved — split-tender adds a parallel employee-paid rail, it doesn’t soften the company’s hard cap. Refunds apply LIFO (employee portion first, then company); the employee’s personal payment is private from the company per CLAUDE.md §6.

ONE_TIME_SETUP (ungated, GivesBack-eligible)

Public storefronts. Anonymous shoppers self-pay at Stripe Checkout. allowsGivesBack: true so the company can layer a fundraising markup that YCM holds as a custodial liability and remits monthly. Five verticals:

  • churches
  • teams
  • community-cause
  • events
  • travel-tourism

There is no monthly subscription for these. A one-time $49 setup fee at intake; after that, YCM’s revenue is per-order margin only.

MEMBER_PAYS (gated, self-pay)

Gated storefronts (invite-only employees) but employees self-pay at Stripe Checkout. No company-side billing post-setup. One vertical:

  • departments

This family was added in Phase 7a / ADR-0094 to serve municipal and educational departments whose budgets can’t fund subscriptions but whose employees can still buy.

Plus three post-launch stubs (active=false)

celebrations-occasions, restaurants-hospitality, sports-teams-uniforms. Schema entries exist; no go-to-market.

The five non-negotiables

Read these as if they were physical laws. They’re enforced at the service layer and the database layer — not just in the UI:

  1. Print files gate orders. No order can ship until every print file for every enabled SKU is APPROVED. Enforced as a hard block in OrdersService, not a soft warning.
  2. Spend limits are hard limits. Order submission acquires a row-level lock on SpendRecord. An employee never overshoots their cap, even under concurrent submissions.
  3. Currency never changes mid-contract. Company.billingCurrency is set at intake from the country field and is immutable through normal flows.
  4. Cross-tenant isolation is absolute. Every query on tenant data includes a companyId filter. PostgreSQL row-level security is a backstop — application code must independently be correct.
  5. Employees never see payment information. No credit card fields, no invoice totals, no billing details of any kind in the storefront. Commerce is between YCM and the company only — except in MEMBER_PAYS, where it’s between YCM and the individual employee directly (still no cross-employee visibility).

A sixth, learned the expensive way after Phase 7a: fundraising markup is custodial, not revenue. YCM holds it as a balance-sheet liability until monthly remittance to the host org. It does not appear in MRR. It does not generate T4A from YCM.

What’s next


Canonical sources

  • CLAUDE.md §2-§6 — the business, the verticals, the five non-negotiables.
  • prisma/migrations/20260425_seed_canonical_verticals_and_suppliers/migration.sql — the original eight verticals.
  • prisma/migrations/20260511_phase7a_departments_and_member_pays/migration.sql — Departments + MEMBER_PAYS.
  • ADR-0094 — the MEMBER_PAYS family.
  • ADR-0104 — split-tender employee top-up (Accepted, not yet shipped).

Triggers for update

Update this chapter if you:

  • Add or retire a vertical (active or stub).
  • Add or change a pricing-model family.
  • Ship a “build not yet started” or “build queued” feature mentioned in this chapter (currently: split-tender employee top-up). When it ships, drop the “build not yet started” parenthetical.
  • Add a sixth user role.
  • Change a non-negotiable in CLAUDE.md §6.