MVS Pay
This is the internal engineering documentation for MVS Pay — the payment platform for MVS Cloud Holdings. It is written for the engineer taking over the platform: it is precise about what is built, and honest about what is stubbed, deferred, or not yet provisioned.
State of the world (as of the 2026-05-29 handoff). All planned work —
Phases 0–5, milestones M0–M28 — is built and merged to main, followed by
two rounds of gap-audit remediation. The repo reports green locally (677 tests).
Nothing is deployed yet. The remaining gap to “working in production” is
almost entirely infrastructure, secrets, external-account provisioning, and a
domain cutover — not application code. See
Deployment Readiness.
What MVS Pay is
MVS Pay is a payment-orchestration platform, built on a self-hosted
deployment of Hyperswitch — Juspay’s open-source
(Apache-2.0) payment orchestration platform, pinned to v1.123.1. Hyperswitch
is the source of truth for payment_intent, payment_attempt, refund,
dispute, mandate, customer, payment_method, merchant_account, connector
configuration, and routing rules. MVS code wraps it, adds the MVS-specific
features Hyperswitch does not model, and exposes everything to other MVS apps as
two published packages.
The product framing is important: MVS Pay is a package other MVS apps consume.
Consumers (healthos, mvs-c2, and apps/mvs-pay itself) talk to payments only
through the published @mvs/mvs-pay-sdk — a typed server-side Node HTTP client.
They never touch Hyperswitch directly.
The platform was pivoted from a previous Finix-direct architecture. The
deleted apps/payfac-gateway, the @mvs/payments Finix client, packages/payfac-jobs,
and the 43-table Finix Prisma schema are gone. Finix is now just one connector
among five (Stripe, Adyen, Cybersource, Braintree, Finix), plugged in through
Hyperswitch’s merchant_connector_account surface. The pre-hyperswitch-pivot
git tag marks the last commit before the pivot, for recovery.
The single defining win of the pivot is the PCI boundary collapse: the PCI
ring narrows to just the Hyperswitch router + card-vault containers. Cards are
tokenized client-side via Hyperswitch’s HyperLoader directly into the vault, so
apps/mvs-pay, apps/mvs-pay-extensions, the pay-crons worker, and both
published packages only ever see Hyperswitch IDs and tokenized references —
never raw PANs, never connector credentials. They are out of PCI scope.
The big picture
MVS Pay is a five-layer system. Requests flow from a consumer app, through the SDK, into the MVS orchestration service, and out to Hyperswitch and its connectors. Card data takes a separate browser-side path straight into the vault.
What each layer does
The guiding architectural principle is maximum native-Hyperswitch bias (ADR-007): surface every Hyperswitch feature through the SDK or UI, and require a counter-ADR before rebuilding anything MVS-side. As a result, Smart Retries, decision-engine MAB routing, Cost Observability, and Revenue Recovery / dunning are all delegated to Hyperswitch. MVS keeps only what Hyperswitch does not model: Plaid-backed instant payouts, POS device inventory, residuals, the custom billing platform, and subscription state.
Repository shape
A pnpm + Turbo monorepo. Two apps and 14 packages (four first-party, ten
transitional vendored copies of mvs-packages foundations pinned at 0.1.0).
The ten vendored @mvs/* packages (@mvs/cache, @mvs/config, @mvs/env-config,
@mvs/i18n, @mvs/logs, @mvs/mail, @mvs/secrets, @mvs/shared, @mvs/ui,
@mvs/utils) are transitional copies of foundations that will live in
mvs-packages. When upstream
publishes, this repo deletes the local copy and flips workspace:* → ^x.y.z.
Toolchain at a glance
Start here
Clone, install, run the two apps locally, and understand the env vars and Infisical paths you need. The fastest path to a running dev environment.
The Hyperswitch pivot in depth: the five layers, the PCI boundary, the schema strategy (sidecar extension tables keyed by opaque Hyperswitch IDs), and the data flows for charges, checkout, webhooks, and recovery.
The mvs-pay-extensions HTTP surface, generated from OpenAPI: payments,
refunds, payouts, customers, merchants, routing, smart-retries,
revenue-recovery, instant-payouts, plaid, disputes, devices, settlements,
mandates, cost-observability, payment-links, billable-items, iias-export,
and the inbound webhook ingests.
The GitOps layer: Argo CD + Kustomize + Helm under deploy/, the EKS
payments namespace, the two Neon projects, Infisical secret paths, and the
CI/CD pipeline on Buildkite.
The honest gap analysis: what must be provisioned, the GitOps blockers, the
domain cutover to www.mvspay.io, and the surfaces still stubbed at 501.
Read this before promising a production date.
19 operational runbooks: incident response, on-call, connector config, secrets rotation, Hyperswitch upgrade, OLAP, decision-engine, Plaid, wallet validation, and the cross-repo cutovers.
Honest accounting of gaps
This is internal documentation; accuracy beats polish. The platform is built and coherent, but a new owner should know exactly where the edges are.
The single most important fact: nothing is deployed, and the GitOps layer cannot reconcile as-is. Two verified hard blockers gate the EKS half of the system:
- Every Argo CD
Applicationpoints at the wrong Git repo. All five manifests underdeploy/argo/*-application.yamluserepoURL: https://github.com/abzholdings/mvs-pay.git. If the real repo is underMVS-CLOUD, Argo CD syncs nothing. One-line-per-file fix; total blocker. - Kustomize image references are placeholders. The extensions and
pay-crons-workerdeployment bases and prod overlays carry literalREPLACE_ME_REGISTRY/REPLACE_ME_IMAGE_SHA/AWS_ACCOUNT_IDstrings.
See Deployment Readiness for the full, prioritized checklist.
Deferred by ADR (decisions, not defects)
- Auth — the one with real security weight.
apps/mvs-pay-extensionstrusts a single sharedMVS_PAY_EXTENSIONS_INTERNAL_SECRET(x-internal-secret). Per-user JWT validation viamvs-authis deferred (ADR-010). Until it lands, an upstream consumer compromise has full payments blast radius, andphiAudit.logAccessrecordsorganizationIdbut anulluserId. - Mobile SDKs — web-only by decision (ADR-009). No iOS/Android/RN wrappers.
- Hybrid Merchant-of-Record Platform-Org tier — the opt-in toggle
(
MerchantExtension.isPlatformOrgMember) ships, but the full Hyperswitch Platform-Organization sub-merchant KYC integration is deferred (ADR-002). There is no real KYC gate today: “approved” is inferred from any ENABLED connector.
Honest 501 stubs (decide launch scope)
These routes return an explicit HTTP 501 today, not a fake success:
- Terminal device lifecycle —
devices/[id]/{activate,action,idle-image,idle-image/upload}. Devices is list/read-only; merchants cannot control physical POS terminals. - Hosted checkout + self-serve billing portal —
billing/checkout-sessionsandbilling/customers/[id]/portal-session. - Merchant KYC remediation —
onboarding/{remediation,resubmit}(tied to the deferred MoR tier above).
Other things to know
- Cost Observability shows real numbers only if the OLAP profile (Kafka + ClickHouse) is provisioned; otherwise it returns a deep-link and renders an empty state.
- CI under-tests the UI. The Buildkite pipeline runs
turbo run test --filter="!mvs-pay" --filter="!mvs-pay-extensions"(extensions has its own step); there is nomvs-payapp test step. The “677 tests passing” figure is a local full run, not a CI guarantee. Both Playwright e2e suites are non-blocking (soft_fail/test.skipon unset sandbox creds). - Finix vocabulary persists as a drift trap. Field names like
finixMerchantId/finixEnablednow carry Hyperswitch IDs;apps/mvs-pay/lib/api-client.tsis still a full Finix error-code map. - Cross-repo cutovers are pending.
mvs-c2’sapps/platform-adminstill imports the deleted@mvs/payfac-clientand is broken until cut over;healthosmust move to@mvs/mvs-pay-sdk@1.0.0(a./legacyshim subpath covers a 30-day overlap). See the cutover runbooks under Operations.
Key decisions (ADR index)
The architecture is governed by eleven ADRs under Concepts → ADRs:
Reference
- Hyperswitch docs: https://hyperswitch.io/
- Hyperswitch source: https://github.com/juspay/hyperswitch
- Hyperswitch API reference: https://api.hyperswitch.io/
- Hyperswitch Control Center: https://github.com/juspay/hyperswitch-control-center
- In-repo source of truth:
README.md,docs/HANDOFF.md,docs/architecture/hyperswitch-pivot.md,docs/deploy-contract.md,docs/cd-ownership.md, and the ADRs underdocs/architecture/adrs/.