Repository Structure
mvs-pay is a single pnpm + Turborepo monorepo for MVS Cloud Holdings’ payment
platform. It owns the merchant/operator UI at pay.mvscloud.com, the
Hyperswitch-orchestration service apps/mvs-pay-extensions, the dedicated
MVS_PAY_DATABASE_URL Postgres + Prisma client, the pay-crons Temporal
worker, and @mvs/mvs-pay-sdk — the partner SDK consumed by every other MVS
app.
This page is the map. It covers the workspace globs, the 2 apps, the 4
first-party packages, the 10 transitional vendored @mvs/* packages, the
tooling/, deploy/, docs/, tests/, and scripts/ directories, and how
the pnpm workspace and Turbo task graph tie them together.
The Hyperswitch pivot is complete. The contract is Hyperswitch +
apps/mvs-pay-extensions + @mvs/mvs-pay-sdk + @mvs/mvs-pay-ui as the only
payment surface. The legacy Finix-direct surface (the deleted
apps/payfac-gateway and its in-repo @mvs/payments HTTP client) has been
removed. See The Hyperswitch Pivot.
Top-level layout
The repository root holds the workspace config, the two task runners’ config, and the seven content directories.
The workspace globs are declared in pnpm-workspace.yaml:
Everything under those three globs is a pnpm workspace package. deploy/,
docs/, tests/, scripts/, and fern/ are not workspace packages —
they hold no package.json and are not part of the install or Turbo graph
(tests/e2e/ is driven by the root test:e2e script and the mvs-pay app’s
own Playwright config, not by Turbo).
Toolchain pins
Apps
Two deployable surfaces live under apps/*. Both are private: true and
versioned at 0.1.0; neither is published.
apps/mvs-pay (UI)
The Next.js app. Key entry points:
It is deployed on Vercel, not Kubernetes — it has no manifest under deploy/.
See Merchant & Operator UI and
Deployment Topology.
apps/mvs-pay-extensions (service)
The Hono service that fronts Hyperswitch. Key entry points:
It is the only first-party surface in the K8s payments namespace that talks to
Hyperswitch; raw connector credentials never reach it — they live only in
Hyperswitch’s merchant_connector_account config, so application code stays out
of PCI scope. See Extensions Service and the
API Reference tab for its HTTP surface.
Some routes in apps/mvs-pay-extensions are honest 501 stubs or are gated
behind deferred work (e.g. some auth paths follow
ADR-010 · Internal secret / deferred auth).
Do not assume every documented endpoint is fully implemented — confirm against
src/routes/ and the per-feature pages under Features
before relying on one.
Packages
packages/* holds 14 workspace packages: 4 first-party packages that
are the heart of mvs-pay, and 10 transitional vendored copies of
foundational @mvs/* packages.
First-party packages (4)
These are the packages this repo actually owns and develops.
The package directory and the npm name don’t always match. packages/mvs-pay/
is the Prisma client @mvs/mvs-pay (the database layer) — not the
apps/mvs-pay UI and not the published SDK. And
packages/temporal-workflows/ publishes as @mvs/pay-temporal-workflows. Read
the directory’s package.json name field before assuming.
@mvs/mvs-pay-sdk and @mvs/mvs-pay-ui are the two published packages —
the public surface of mvs-pay for other repos. They are a linked release
group in Changesets, so they version together:
“Published” here means published to a restricted (private) npm scope —
publishConfig.access: "restricted" on both packages. They are not public on
npmjs.org. When you change the public surface of either, run pnpm changeset
and commit the generated markdown with your PR.
@mvs/pay-temporal-workflows (the worker package)
This is the source for the pay-crons-worker deployment. See
Temporal Worker.
@mvs/mvs-pay (the Prisma client)
Scripts of note: db:generate, db:push, db:migrate, db:migrate:deploy,
db:migrate:status, db:studio. The db:generate Turbo task is wired as a
dependency of build, type-check, and test (see the task graph below). The
DB is Neon, per ADR-004 · Neon, not RDS;
see also the Data Model page.
Transitional vendored packages (10)
The following 10 packages are transitional vendored copies of foundational
healthos packages that, in the target architecture, will live in the separate
mvs-packages repo. They are all
private: true, pinned to 0.1.0, and ignored by Changesets.
The publish-then-flip cutover
These packages are vendored temporarily. The cutover plan is mechanical and per-package:
Today — vendored copy
The package exists in this repo under packages/<name>/, pinned to 0.1.0, and
consumers depend on it via workspace:*. It is in the Changesets ignore list,
so it never appears in the release group and its version is not bumped by the
SDK/UI release.
mvs-packages publishes
When mvs-packages publishes the
real @mvs/<name> at some x.y.z, the canonical source moves out of this repo.
The authoritative cutover policy is in
docs/deploy-contract.md §“Boundaries with other repos”.
Treat the 10 vendored packages as read-mostly mirrors, not first-party code.
Bug-fixing them here is acceptable as a stopgap, but the durable fix belongs in
mvs-packages — otherwise the flip will silently drop your local change. The
two genuinely owned UI/runtime libraries to evolve here are the first-party
@mvs/mvs-pay-sdk and @mvs/mvs-pay-ui.
Tooling
tooling/* holds two workspace-private packages that exist only to configure
the others. Neither is published; both are private: true at 0.1.0.
tooling/typescript/ also ships the ambient declaration files that paper over
cross-repo Prisma generation until it lands natively:
The cross-repo Prisma stubs are a documented stopgap: this repo consumes runtime
peer Prisma clients (@prisma/control-plane-client, @prisma/tenant-client)
from healthos until cross-repo Prisma generation lands. See the
Cross-repo Prisma Build runbook.
Other directories (not workspace packages)
These directories are part of the repo but outside the pnpm workspace globs.
deploy/ — Kubernetes manifests
GitOps-driven Kubernetes manifests for the payments stack, all reconciled by Argo CD (ADR-005 · Argo CD, not Flux). Two delivery styles are used by workload.
apps/mvs-pay (the UI) is deployed on Vercel and has no manifest here. See
GitOps & Kubernetes and
Deployment Topology.
docs/ — engineering docs (source)
tests/ — repo-level E2E
tests/e2e/ holds the Playwright end-to-end suite that exercises real payment
flows. Run via the root test:e2e script:
Specs cover the payment surface end-to-end — for example
payment-success.spec.ts, payment-decline.spec.ts, smart-retries.spec.ts,
dynamic-routing.spec.ts, routing-priority.spec.ts,
routing-volume-split.spec.ts, plaid-link.spec.ts,
click-to-pay.spec.ts, mandate-amend.spec.ts, surcharge.spec.ts,
taxjar.spec.ts, webhook-idempotency.spec.ts,
connector-onboarding.spec.ts, connector-disable-enable.spec.ts, and
cost-observability.spec.ts. tests/e2e/docker-compose.hyperswitch.yml brings
up a local Hyperswitch for the suite; tests/E2E_TESTING_PLAN.md documents the
plan.
This repo-level suite is separate from the per-package Vitest unit tests
(the test Turbo task) and from the apps/mvs-pay app-scoped Playwright suite
(under apps/mvs-pay/tests/). The README cites a green suite of 677 tests —
that figure spans all of these; this page does not re-derive the count.
scripts/ — CI guards
Workspace + Turbo task graph
How packages resolve each other
pnpm links workspace packages by their workspace:* dependency ranges. A few
install-time settings in pnpm-workspace.yaml are load-bearing:
catalog:— single source of truth fordotenv,typescript(6.0.3), andzodversions; packages referencecatalog:instead of pinning.publicHoistPattern+shamefullyHoist: true— Prisma,@prisma/*, and Better Auth are hoisted so Vercel/Next serverless bundlers can resolve them from any package.overrides— pins to keep the dependency tree coherent, notably the@aws-sdk/*credential surface aligned with Infisical’s bundled version, andhono/next/esbuildfloors.peerDependencyRules.allowedVersions— allows React 19, Zod 4, and the Better Auth peer set.
The Turbo task graph
turbo.json defines the task pipeline. The root package.json scripts are thin
wrappers over turbo run <task>. The key dependency edges:
Reading the edges:
builddepends on upstream packages’build(^build), upstreamdb:generate(^db:generate), and its owndb:generate. So the Prisma client (@mvs/mvs-pay) is generated before anything that imports it builds.type-checkdepends on^build,^type-check,^db:generate, and its owndb:generate.testdepends on^buildanddb:generate(own + upstream); it emitscoverage/**andjunit.xmloutputs.db:generateiscache: falseand keyed onprisma/schema.prisma+prisma.config.tsinputs.devandstartarepersistentand uncached;startwaits on^build.
Concurrency is capped at 20. globalDependencies invalidate the whole cache
when **/.env.*local, tsconfig.json, or .nvmrc change. turbo.json also
declares a large globalPassThroughEnv allow-list (Infisical creds, the
MVS_PAY_* / HYPERSWITCH_* / PLAID_* / TEMPORAL_* / RESEND_* / Datadog
vars, etc.) so secrets reach tasks without busting the cache — the full set is
catalogued on the Environment Variables
page.