NEXUS
Resources

CloudPay Resources

The repository, how to run CloudPay locally, how a change reaches production, the tests, and the traps.

Clover-CloudPayBoth apps: src/web (Next.js) and src/api (.NET). main is production; development is the development environment.Azure DevOps
Azure PipelinesFour pipelines: web and API, each for development and production. Production deploys wait for approval.Azure DevOps
Clover developer docsOAuth, the REST API, Hosted Checkout, webhooks and the Cloud Pay Display SDK. Development runs against Clover's sandbox.Clover
ClerkUsers, sign-in, invitations and user metadata. One instance per environment.Auth
DMS serviceMiCamp's dealer management service, which serves Reynolds and Reynolds data to CloudPay and stores each merchant's Reynolds settings.MiCamp
Twilio · SMTP2GOText messages and email, all sent by the API.Messaging
Microsoft ClaritySession analytics for the production web app.Microsoft
Test suites185 xUnit test methods on the API, run by its pipeline, and 163 Vitest tests on the web app, run locally only.xUnit · Vitest
Sales deckA generated 14-slide deck with speaker notes (US 2922). The copies of record are attached to the work items; the generator is under documentation/marketing.PowerPoint

Run it locally

  1. 1
    PrerequisitesNode 20 (npm 11 keeps the lockfile unchanged), the .NET 10 SDK, and az login with access to the development Key Vault, which the API reads. dotnet-ef only if you add migrations.
  2. 2
    Installnpm i at the root installs only concurrently; npm run setup runs npm ci in src/web.
  3. 3
    Configure the web appCopy src/web/.env.example to src/web/.env.local, set NEXT_PUBLIC_BACKEND_URL=http://localhost:5085, and fill in the Clerk, Clover and DMS values from the team. Use the NEXT_PUBLIC_ names for the Clover domain and remote app id.
  4. 4
    Run bothnpm run dev starts the web app on http://localhost:3000 and the API on http://localhost:5085 (HTTPS on 7291 with --launch-profile https). Swagger is at /swagger.
From the repository root
npm i && npm run setup
npm run dev                         # web :3000 + api :5085
npm run dev:web                     # or one side only: npm run dev:api

cd src/api && dotnet test CloudPayClover.slnx
cd src/web && npm run typecheck && npm test
A new migration (from src/api)
dotnet ef migrations add <Name> --project src/CloudPayClover.Infrastructure --startup-project src/CloudPayClover.Api
dotnet ef database update      --project src/CloudPayClover.Infrastructure --startup-project src/CloudPayClover.Api

Getting a change to production

  1. 1
    BranchOff development, named after the story: us/{story}/….
  2. 2
    Test locallyNo pipeline runs on a PR, and the web pipeline runs no tests or lint at all. Run dotnet test and npm run typecheck && npm test yourself.
  3. 3
    PR into development1 reviewer, every comment resolved, the PR template filled in. Complete it with Merge (no fast-forward).
  4. 4
    Development deployMerging runs the pipeline of each app you touched (path filters on src/web and src/api) and deploys it to the development environment.
  5. 5
    Release PRdevelopment into main, with 2 reviewers.
  6. 6
    ApproveThe production deploy waits on the production environment until someone approves it in Azure DevOps.
  7. 7
    VerifyThe API pipeline polls /health and the web pipeline checks that / does not answer 5xx. Then take a sale and send an invoice.

There are no version numbers or tags: src/web/package.json has said 0.1.0 since the first commit. Releases go by date and PR.

Tests

API: xUnit with EF Core InMemory and Moq (src/api/tests), run by the API pipelines
ClassTestsCovers
ReceiptServiceTests21Validation, merchant checks, Clover lookups, email and SMS, partial delivery, refund receipts
MerchantUserServiceTests15Users list filters and sorting; update rules and the Clerk sync
ContactServiceTests13Demo-request validation, the honeypot, delivery failures, the disabled form
InvoiceTemplateTests12Invoice emails and texts: DMS and plain, logo, encoding, subjects, truncation
PaymentServiceRefundTests12Refund webhooks: invoice refunded, partially refunded or voided; idempotency
InvoiceCommandServiceTests9Create and resend: contact rules, the DMS snapshot, the pay link
PaymentServiceInvoiceLinkTests9Matching payments to invoices, paid emails, no resend on a replay
UserServiceTests9Preference defaults, saving and validation
MerchantUsersEndpointTests8/merchant/users: auth, scoping, role validation, domain errors
PaymentServiceLookupTests7Lookups by reference, single and batch
AdminMerchantUsersEndpointTests6Admin users routes: 401, 403 for merchant admins, route scoping, role validation
MerchantServiceSettingsTests6Saving settings, time zone, display groups, older JSON
PaymentServiceRefundNoteTests6Refund notes: create, update, scoping, keys
UserPreferencesEndpointTests6Preferences GET and PUT: auth, scoping, 404, a bad time zone
ClerkSessionContextResolverTests5Clerk user to merchant and role, deactivated merchants, caching
ContactEndpointTests5Demo request: 202, 400, 503, 429 past the limit
DemoRequestTemplateTests5The demo-request email
HostedCheckoutEndpointTests4Redirect to Clover; the paid, missing and failing pages
MerchantLogoServiceTests4Logo sync from Clover
WireEnumExtensionsTests4Clover's transaction type and result strings
Seven more classes19Admin settings, the Clover checkout request, hosted checkout, invoice stats SQL, receipt logos, webhook routing, Data Protection keys

185 test methods in 27 classes; five theories bring it to 194 cases. Tests use EF Core's in-memory provider, so SQL Server behaviour is not exercised (the invoice-stats test only checks the generated SQL). Nothing covers the Clover OAuth exchange or the token refresh.

Web: Vitest with jsdom and Testing Library (`**/__tests__/**`)
AreaFilesTests
DMSDmsService, dmsTableQuery, dmsServer, useDmsOrders, PaymentHistoryCell, SendDialog47
Helperstime zones, payment reference, percent input, card brands, contact validators, Clarity35
Payment detailsPaymentDetailsService28
InvoicesInvoiceSubmit, createInvoice, lookupInvoiceByPayment15
DevicesDeviceStatusPanel, useFavoriteDevice15
MarketingDemoRequestForm, MarketingFooter, the demo-request schema13
Transactions exportexportCsv8
TablesCustomTableActions2

163 it declarations in 23 files; it.each rows bring it to about 204 cases. The Sales Terminal (PaymentInput, CloverService), the admin pages, Settings, Preferences and the route handlers other than the DMS client have no tests, and no pipeline runs the suite.

Finding your way around the code

FolderWhat is in it
src/web/app/(root)The merchant app behind CloverOnboardingGuard: Sales (page.tsx), dashboard, inventory, dms, payment-details/[id], settings, preferences
src/web/app/(admin), (auth), (marketing)The admin pages behind AdminGuard; sign-in, sign-up, connect-clover and callback; /home and the legal pages
src/web/app/apiThe route handlers: Clover relays, the DMS proxy, the Clerk webhook, CSP reports
src/web/servicesPlain modules the hooks call: api/backendClient, auth (session and token store), clover (SDK and token), dms, invoices, payments, receipts, user
src/web/componentsBy feature, plus ui/ (shadcn) and shared/ (CustomTable, SendDialog, DeviceStatusPanel, NumericKeypad)
src/web/proxy.tsThe Next.js 16 middleware: Clerk, the public routes, the CSP
src/api/src/CloudPayClover.ApiProgram.cs (auth schemes, policies, OData, the rate limit), the controllers and the error middleware
…Application, …DomainInterfaces, DTOs and commands; entities, enums and exceptions (no dependencies)
…InfrastructureCloudPayDbContext, configurations and migrations; the services; the Clover, Clerk, Twilio and SMTP2GO clients; email and SMS templates; background services; DependencyInjection.cs
Database tables (EF Core migrations, applied on startup)
TableHoldsSince
MerchantsThe business, its Clover id (unique), the active flag and the Settings JSON2026-01-27; settings 2026-02-12
MerchantTokensEncrypted Clover access and refresh tokens, their expiries and status (Active, Expired, Revoked, RefreshFailed)2026-01-27
PaymentsClover payments and refunds from webhooks: cents, card, type, result, external reference, and for refunds the source payment2026-01-27; refund link 2026-06-10
WebhookLogsEvery Clover webhook, its processing state and its retries2026-01-27
InvoicesInvoices, their contacts and status, and the DmsSnapshot JSON2026-04-15; both channels 2026-05-13; snapshot 2026-07-31
DataProtectionKeysThe key ring that encrypts the Clover tokens, shared by every instance2026-05-28
UsersClerk id, merchant, role, department, name, email and the Preferences JSON2026-06-23; profile fields 2026-08-12
InvitationsClerk invitations with their role and status2026-06-23
RefundNotesRefund reasons, unique per merchant and source payment2026-06-26

Traps that cost real time

  • There are two copies of payments. The Dashboard reads Clover live; the DMS pages and payment details read the API's webhook copy. A payment can show in one before the other, and webhooks arrive only where Clover is configured to send them.
  • Cents and dollars. Payments amounts are cents; Invoices amounts are dollars. The invoice matcher divides by 100 before comparing.
  • Invoice status is a number on the wire. New statuses were appended so that existing numbers stay put; keep it that way.
  • `Settings` and `Preferences` are JSON columns without value comparers. Assign a new object, or mark the property modified, or EF Core will not save an in-place change.
  • There are two token systems. The Clerk token lasts about a minute; the API's JWT lasts 60 minutes and lives in memory. Respect the refresh-and-retry code and its comments: clearing the settings cache on a recoverable 401 was a past bug.
  • Settings and time-zone changes travel as DOM events (merchant-settings-changed, cloudpay-timezone-changed), not React context. Dispatch them when you change either.
  • Keep the DMS hooks gated (enabled) on Reynolds and the department toggle, or they fail with not_provisioned (409).
  • `CustomTable` in server-side mode expects only the current page plus totalCount. Passing the full list pages it twice.
  • `proxy.ts` is the middleware in Next.js 16. Do not add a middleware.ts.
  • `npm start` does not work locally: server.js exists only in the standalone build. npm run lint runs next lint, which Next.js 16 removed, so it probably fails.
  • The API's git history is not here. The monorepo merge (PR 325) was squashed, so git log src/api starts on 2026-09-28. Older API history is in the read-only Clover-CloudPayService repository. git log --follow does work for src/web.
  • Dead code is still in the tree: services/mockUpData, the mocks in lib/index.ts, services/pdf/GeneratePDF.ts, lib/oauth-session, several unused components, and the unused nodemailer, ssh2-sftp-client and jszip packages. Email and SMS are sent by the API only.
  • `CloudPayClover.Api.http` is a template leftover (GET /weatherforecast).
Found a security issue?Report it through the bug bounty program instead of public channels.