Repository Structure

Monorepo layout

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.

mvs-pay/
├── apps/ # 2 deployable surfaces
│ ├── mvs-pay/ # Next.js merchant + operator UI (pay.mvscloud.com)
│ └── mvs-pay-extensions/ # Hono Hyperswitch-orchestration service
├── packages/ # 14 workspace packages (4 first-party + 10 vendored)
├── tooling/ # @mvs/tailwind-config + @mvs/tsconfig (workspace-private)
├── deploy/ # Kubernetes manifests (Helm + kustomize) + Argo CD apps
├── docs/ # architecture, ADRs, runbooks, deploy contract, HANDOFF
├── tests/ # repo-level Playwright E2E suite (tests/e2e/)
├── scripts/ # CI guards (deleted-package guard, PHI audit)
├── fern/ # this documentation site (Fern)
├── package.json # root scripts → turbo; devDeps: turbo, biome, changesets
├── pnpm-workspace.yaml # workspace globs, catalog, overrides, hoist rules
├── turbo.json # task graph + global env passthrough
├── biome.json # lint + format config
├── .changeset/config.json # linked release group: @mvs/mvs-pay-sdk + @mvs/mvs-pay-ui
└── .nvmrc # Node >=24

The workspace globs are declared in pnpm-workspace.yaml:

1packages:
2 - apps/*
3 - packages/*
4 - tooling/*

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

ToolVersionWhere pinned
Node>=24engines.node in root package.json; .nvmrc
pnpm11.4.0 (with sha512)packageManager in root package.json
Turborepo^2.9.15root devDependencies
Biome2.4.16root devDependencies (lint + format)
Changesets^2.31.0root devDependencies
TypeScript6.0.3pnpm catalog: (in pnpm-workspace.yaml)

Bootstrap with Corepack so the exact pinned pnpm is used:

$corepack enable
$corepack prepare pnpm@11.4.0 --activate
$pnpm install --frozen-lockfile

See Local Development for the full setup.

Apps

Two deployable surfaces live under apps/*. Both are private: true and versioned at 0.1.0; neither is published.

AppnamePathRuntimePurpose
Merchant + Operator UImvs-payapps/mvs-pay/Next.js (App Router), VercelMerchant + operator payments UI served at pay.mvscloud.com. Dev port :3002.
Extensions servicemvs-pay-extensionsapps/mvs-pay-extensions/Hono, K8s payments namespaceWraps Hyperswitch’s API with MVS orchestration (Plaid instant payouts, POS device management, residuals, custom billing). Reached only via @mvs/mvs-pay-sdk. Dev port :3005.

apps/mvs-pay (UI)

The Next.js app. Key entry points:

apps/mvs-pay/
├── src/app/ # App Router routes (merchant + operator surfaces)
├── src/components/ # app-local components (composes @mvs/ui, @mvs/mvs-pay-ui)
├── src/lib/ # app-local helpers
├── instrumentation.ts # OpenTelemetry / Datadog bootstrap
├── proxy.ts # request proxy layer
├── next.config.ts
├── vercel.json # Vercel deploy config
├── playwright.config.ts + tests/ # app-scoped E2E (api/, ui/, utils/, fixtures/)
└── vitest.config.ts

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:

apps/mvs-pay-extensions/
├── src/app.ts # Hono app composition (route mounting)
├── src/server.ts # production server entry
├── src/dev.ts # dev server entry
├── src/routes/ # HTTP route handlers
├── src/services/ # orchestration logic over Hyperswitch + Plaid + Temporal
├── src/middleware/ # auth (internal secret), CORS, rate limiting
├── src/config/
└── src/types.ts

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.

namePathPublished?Purpose
@mvs/mvs-pay-sdkpackages/mvs-pay-sdk/Yes (access: restricted)Typed HTTP client for mvs-pay-extensions. Consumed by every external MVS app that needs to charge / refund / invoice. Builds to dist/ (ESM + CJS + types) with subpath exports ., ./legacy, ./mock, ./iias.
@mvs/mvs-pay-uipackages/mvs-pay-ui/Yes (access: restricted)React components wrapping the Hyperswitch Web SDK (checkout, payment-method picker). Per ADR-009, web-only. Builds to dist/.
@mvs/mvs-paypackages/mvs-pay/No (private)Dedicated mvs-pay Prisma client wrapper bound to MVS_PAY_DATABASE_URL. Owns the schema (prisma/) and migrations via prisma migrate deploy. Source entry src/index.ts.
@mvs/pay-temporal-workflowspackages/temporal-workflows/No (private)The pay-crons Temporal task-queue workflows, activities, schedules, and worker entry.

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:

1// .changeset/config.json
2{
3 "linked": [["@mvs/mvs-pay-sdk", "@mvs/mvs-pay-ui"]],
4 "access": "restricted",
5 "baseBranch": "main"
6}

“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)

packages/temporal-workflows/src/
├── index.ts
├── worker/ # worker entry (pnpm start:worker / start:worker:dev)
├── workflows/ # Temporal workflow definitions
├── activities/ # activity implementations
├── schedules/ # cron schedules (registered via start:schedules)
├── cli/
├── lib/
└── shared/

This is the source for the pay-crons-worker deployment. See Temporal Worker.

@mvs/mvs-pay (the Prisma client)

packages/mvs-pay/
├── prisma/ # the mvs-pay schema (source of truth for the payments DB)
├── prisma.config.ts
├── src/index.ts # generated client wrapper, bound to MVS_PAY_DATABASE_URL
└── vitest.config.ts

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.

namePathRole
@mvs/cachepackages/cache/Redis-backed cache helpers.
@mvs/configpackages/config/Shared runtime config.
@mvs/env-configpackages/env-config/Typed env-var parsing/validation.
@mvs/i18npackages/i18n/Internationalization helpers.
@mvs/logspackages/logs/Structured logging.
@mvs/mailpackages/mail/Transactional email (Resend) + templates (export/preview scripts).
@mvs/secretspackages/secrets/Secret access (Infisical). See Secrets Management.
@mvs/sharedpackages/shared/Shared components, hooks, lib, schemas, shells, api-client.
@mvs/uipackages/ui/The shared design-system component library (shadcn-based primitives + blocks).
@mvs/utilspackages/utils/General utilities.

The publish-then-flip cutover

These packages are vendored temporarily. The cutover plan is mechanical and per-package:

1

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.

2

mvs-packages publishes

When mvs-packages publishes the real @mvs/<name> at some x.y.z, the canonical source moves out of this repo.

3

Flip the dependency

This repo deletes the local packages/<name>/ copy and changes every consuming package.json dependency from workspace:* to ^x.y.z (the published range). The package leaves the workspace globs entirely.

4

Guard against stragglers

scripts/check-deleted-package-references.sh (run in CI) fails the build if any code or user-facing doc still references a deleted package. This is the same guard that enforces the removal of the legacy @mvs/payments / payfac-gateway surface.

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.

namePathPurpose
@mvs/tailwind-configtooling/tailwind/Shared Tailwind v4 theme (theme.css) + tailwind-animate.
@mvs/tsconfigtooling/typescript/Workspace-private tsconfig presets (base.json, nextjs.json, react-library.json, node) + Prisma peer-dep type stubs.

tooling/typescript/ also ships the ambient declaration files that paper over cross-repo Prisma generation until it lands natively:

tooling/typescript/
├── base.json
├── nextjs.json
├── react-library.json
├── prisma-stubs.d.ts # generic Prisma client stub types
├── mvs-pay-prisma-stubs.d.ts # @mvs/mvs-pay client stubs
└── cross-repo-stubs.d.ts # peer Prisma clients from healthos

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.

PathWorkloadDelivery
deploy/hyperswitch/Hyperswitch core (router, producer, consumer, drainer, control-center, card-vault, OLAP)Upstream Helm chart (hyperswitch-app) with values-{prod,staging}.yaml overrides + an ExternalSecret. The chart is not forked.
deploy/decision-engine/juspay/decision-engineSmall in-repo Helm chart (Chart.yaml, values.yaml, templates/).
deploy/mvs-pay-extensions/apps/mvs-pay-extensions imagekustomize base/ + overlays/{prod,staging}/.
deploy/pay-crons-worker/packages/temporal-workflows worker imagekustomize base/ (Deployment + register-schedules Job; no Service) + overlays.
deploy/payments-namespace/Namespace policyIstio PeerAuthentication + DestinationRule.
deploy/argo/All of the aboveArgo CD Application CRs (one per workload).
deploy/aws-waf/EdgeWAF rule definitions.
deploy/datadog-monitors.yamlObservabilityDatadogSLO + DatadogMonitor CRs.
deploy/secretstore-infisical.yamlSecretsClusterSecretStore (prod + staging).

apps/mvs-pay (the UI) is deployed on Vercel and has no manifest here. See GitOps & Kubernetes and Deployment Topology.

docs/ — engineering docs (source)

PathContents
docs/HANDOFF.mdThe engineering handoff for the new owner.
docs/architecture/hyperswitch-pivot.mdThe pivot plan (milestones M0–M28).
docs/architecture/adrs/Architecture Decision Records (mirrored into this Fern site under Core Concepts).
docs/runbooks/Operational runbooks (mirrored under Operations).
docs/deploy-contract.mdThe inter-repo contract: owners, consumers, PCI boundary, vendored-package cutover.
docs/cd-ownership.mdPer-surface CD ownership.
docs/_archive/Historical material; excluded from the deleted-package CI guard.

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:

$pnpm test:e2e # playwright test --config tests/e2e/playwright.config.ts

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

PathPurpose
scripts/check-deleted-package-references.shFails CI if any reference to a deleted package (e.g. legacy @mvs/payments, or a flipped vendored package) survives in code or user-facing docs. Excludes lockfiles, CHANGELOG.md, _archive/, .changeset/, and build artifacts.
scripts/lint/phi-audit.tsPHI / sensitive-data lint audit.

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 for dotenv, typescript (6.0.3), and zod versions; packages reference catalog: 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, and hono / next / esbuild floors.
  • 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:

  • build depends on upstream packages’ build (^build), upstream db:generate (^db:generate), and its own db:generate. So the Prisma client (@mvs/mvs-pay) is generated before anything that imports it builds.
  • type-check depends on ^build, ^type-check, ^db:generate, and its own db:generate.
  • test depends on ^build and db:generate (own + upstream); it emits coverage/** and junit.xml outputs.
  • db:generate is cache: false and keyed on prisma/schema.prisma + prisma.config.ts inputs.
  • dev and start are persistent and uncached; start waits 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.

Common commands

$pnpm install --frozen-lockfile
$pnpm turbo run type-check # type-check the whole graph
$pnpm turbo run test # unit tests (Vitest), respects the graph
$pnpm turbo run build # build everything (generates Prisma first)
$pnpm build:packages # turbo run build --filter=./packages/*
$
$pnpm --filter mvs-pay dev # UI on :3002
$pnpm --filter mvs-pay-extensions dev # extensions service on :3005
$
$pnpm changeset # required when changing @mvs/mvs-pay-sdk or -ui