From a fresh machine to CloudPay running locally in about an hour: the web app on port 3000 and the API on port 5085, signed in through the development Clerk instance, using the development database and Clover's sandbox. It ends with your first invoice. The guide follows development, the branch all work starts from.
Checked against development at 5dbe126 on .
Install the tools, and ask for access on day one: the access takes longer than the setup.
| Tool | Version | Why |
|---|---|---|
| Node.js and npm | Node 20 with npm 11 or later | The web app. The Docker image builds on node:20-alpine and nothing else pins a version. npm 10, which comes with Node 20, rejects the lockfile: run npm install -g npm@11, as the Dockerfile does. On Node 25 and later the web tests need a flag (see Troubleshooting). |
| .NET SDK | 10 | The API targets net10.0. There is no global.json; this guide was checked with 10.0.400. |
| Azure CLI | Any recent version | az login lets the API read the email and SMS credentials from the development Key Vault |
| Access | From | Needed for |
|---|---|---|
| The Clover-CloudPay project in Azure DevOps | The team | Cloning and pull requests |
| A Clerk account on the development instance, linked to a merchant that is connected to Clover's sandbox | A platform admin: an invitation from the development environment, or Link Existing User for an account you already have | Signing in. Without the link you only see Account Pending Setup. |
| The development Clerk publishable and secret keys, and read access to the development Key Vault | The team | The web app's .env.local, and sending emails and texts from your machine |
| Optional: a Clover dev device on that merchant running Cloud Pay Display, and DMS client credentials | The team | Taking a sale on a terminal, and the DMS pages |
One repository holds both apps. Its default branch is main, which is production, so switch to development after cloning.
node --version; npm --version; dotnet --version # v20.x, 11.x or later, 10.0.x
git clone https://micampdevscottsdale.visualstudio.com/Clover-CloudPay/_git/Clover-CloudPay
cd Clover-CloudPay
git checkout development
npm ci # root: installs only concurrently
npm run setup # runs npm ci inside src/websrc/web is the Next.js web app: run npm there. src/api is the .NET API, solution CloudPayClover.slnx: run dotnet there. The root package.json only forwards (dev, dev:web, dev:api, setup). It is not an npm workspace, because hoisting would break npm ci in src/web and the Docker build. The AGENTS.md files at the root and in each app are the most current guides.
The API runs without edits. appsettings.Development.json already points at the shared development resources, and the http launch profile sets ASPNETCORE_ENVIRONMENT=Development and sends invitation links from your API to http://localhost:3000/sign-up.
| Key | Development value | Locally |
|---|---|---|
ConnectionStrings:DefaultConnection | The shared development database | Keep it, or point it at your own SQL Server (see Choose a database) |
Clover:Environment | sandbox, with the sandbox app's ClientId and ClientSecret | Keep it. Every Clover call from the API goes to the sandbox. |
Clerk:Authority, Clerk:AuthorizedParties, Cors:AllowedOrigins | The development Clerk instance. The parties and the origins include http://localhost:3000. | Take the web app's Clerk keys from the same instance, and keep the web app on port 3000 |
KeyVault:VaultUri | The development Key Vault | Needs az login. When empty, secrets come from the Secrets section instead. |
BackgroundJobs:Enabled | Not set, so true | Set it to false (below) |
By default the API runs the Clover token refresh and the webhook retry jobs. Clover refresh tokens are single-use, so only one API per database may run them (BackgroundJobsSettings). The deployed development API already does, which is why dev2 runs with BackgroundJobs__Enabled=false. Do the same when your API uses the shared database.
$env:BackgroundJobs__Enabled = "false" # Bash: export BackgroundJobs__Enabled=false
az login # lets the API read the Key VaultChange anything else the same way: environment variables override the JSON files, and a double underscore stands for the : in a key. Do not edit the committed appsettings files, and never add real secrets to them.
The web app reads src/web/.env.local, which git ignores. Write it from the block below rather than copying .env.example: that file names the Clover values without their NEXT_PUBLIC_ prefix, so the code never reads them, and lists a DATABASE_URL nothing uses.
# The API on your machine, no trailing slash
NEXT_PUBLIC_BACKEND_URL=http://localhost:5085
# Clerk: the development instance, the same one the API trusts
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=<your-clerk-publishable-key>
CLERK_SECRET_KEY=<your-clerk-secret-key>
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
# Optional: only for the DMS pages
# DMS_API_URL=<dms-service-url>
# DMS_CLIENT_ID=<dms-client-id>
# DMS_CLIENT_SECRET=<dms-client-secret>| Variable | Locally | Notes |
|---|---|---|
NEXT_PUBLIC_BACKEND_URL | Required | Every call to the API, and the Content-Security-Policy in proxy.ts |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY and the Clerk URLs | Required | <ClerkProvider> in the browser and clerkMiddleware in proxy.ts. The URLs keep sign-in and sign-up on the app's own pages. |
NEXT_PUBLIC_TARGET_CLOVER_DOMAIN, NEXT_PUBLIC_REMOTE_APP_ID | Optional | Unset under next dev, the device SDK and the Clover route handlers use Clover's sandbox, and the SDK connects as a built-in app id (services/clover/sdkConfig.ts). For terminal sales, use the development values from the team. |
DMS_API_URL, DMS_CLIENT_ID, DMS_CLIENT_SECRET | Only for the DMS pages | Read on the server only, in lib/dms/dmsServer.ts |
CLERK_WEBHOOK_SECRET, NEXT_PUBLIC_CLARITY_PROJECT_ID | Not needed | Clerk's webhooks go to the development environment, not to your machine. Clarity loads only in production builds. |
The API needs SQL Server. Every time it starts, it applies any pending EF Core migrations before it listens (ApplyMigrationsAsync in Program.cs).
| Option | What you get | Watch out for |
|---|---|---|
| Shared development database (the default) | Merchants already connected to Clover's sandbox, with their users, invoices and the webhook copies of their payments. You can sign in and work at once. | Your branch's migrations change it for everyone, dev2 included, so keep them additive. Turn the background jobs off. |
| Your own SQL Server | A private database. On the first start the 14 migrations create every table. | It starts empty: no merchant, no Clover connection, no users. Connecting a merchant needs a platform admin account and a Clover OAuth round trip back to http://localhost:3000/callback. Clover tokens cannot be copied in: they are encrypted with the key ring in their own database. |
$env:ConnectionStrings__DefaultConnection = "Server=<your-sql-server>;Database=<your-database>;Trusted_Connection=True;TrustServerCertificate=True"BackgroundJobs__Enabled, run npm run dev from the repository root. It starts both apps side by side, with their output prefixed [web] and [api]. The API applies pending migrations first, then listens on http://localhost:5085.http://localhost:5085/health answers Healthy. Swagger UI is at http://localhost:5085/swagger (Development only).http://localhost:3000. Signed out, it sends you to /sign-in; /home is the public landing page.npm run dev:web # from the root: the web app only, port 3000
npm run dev:api # from the root: the API only, port 5085
# From src/api: HTTPS on 7291 as well (needs a trusted ASP.NET Core dev certificate)
dotnet run --project src/CloudPayClover.Api --launch-profile httpsThe API's CORS origins and Clerk's authorized parties both list http://localhost:3000. If Next.js moves to another port because 3000 is busy, the browser blocks the calls to the API or the API answers 401. Free the port instead.
An invoice uses the whole chain: Clerk sign-in, the API session, the database, email through the Key Vault, and Clover's sandbox checkout. It needs no terminal.
http://localhost:3000/sign-in and sign in with your development Clerk account. The onboarding guard calls POST /api/v1/auth/session on your API and opens the Sales Terminal.Outstanding, logs Created invoice … for merchant … and sends the email. Dashboard, Invoice Transactions now lists it.http://localhost:5085/api/v1/hosted-checkout/{invoiceId}, which is your own API, so open it on this machine. It redirects to Clover's sandbox checkout page.POST http://localhost:5085/api/v1/invoices
Authorization: Bearer <api-jwt-from-auth-session>
Content-Type: application/json
{
"amount": 1, "taxAmount": 0, "tipAmount": 0, "totalAmount": 1,
"sendToEmail": "<your-email>", "sendToSMS": "",
"orderId": null, "cloverPaymentId": "", "serviceAdvisorId": null,
"employeeId": null, "dmsSnapshot": null
}The invoice is saved even when sending fails; the API then logs Failed to send invoice … via email. Most often it could not read the SMTP2GO credentials from the Key Vault. Run az login with an account that has access, then press Resend in Invoice Transactions.
With a Clover dev device on the merchant, take a sale too: on Clover Payment pick the device and press Connect to Device, wait for Ready, enter an amount and press Send to {device}. The customer side then runs on the device.
No pipeline runs on a pull request, and the web pipeline runs no tests at all, so run both suites before you open one. Neither needs the database: the API tests use EF Core's in-memory provider and the web tests use jsdom.
cd src/api
dotnet test CloudPayClover.slnx # or one class: dotnet test --filter "FullyQualifiedName~InvoiceCommandServiceTests"
cd ../web
npm run typecheck
npm test # or one file: npx vitest run services/dms/__tests__/DmsService.test.ts| Command | Result |
|---|---|
dotnet test CloudPayClover.slnx | 277 xUnit tests in two projects. 275 passed; 2 failed on the Windows 11 machine used for this guide (see Troubleshooting). |
npm run typecheck | Passes |
npm test | 227 Vitest tests in 29 files, all passing (run on Node 26 with web storage turned off) |
npm run lint | Fails: it runs next lint, which Next.js 16 removed |
| Symptom | Cause and fix |
|---|---|
Every web test fails with Cannot read properties of undefined (reading 'clear') | Node 25 and later define their own localStorage, which hides jsdom's. Use Node 20, or turn Node's web storage off for the run (below). |
Two API tests fail on Assert.DoesNotContain | Sms_TruncatesLongCustomerNames_WithAsciiEllipsis and SendAsync_EmailBody_OmitsCardRowForCashPayments compare strings with the current culture. On the machine used for this guide they failed under .NET's default ICU globalization and passed with NLS (below). If only these two fail, the cause is the machine, not your change. |
| The API stops at startup with a SQL error | Migrations run before the API listens, so it must reach the database in ConnectionStrings:DefaultConnection. Check the value and your network, and ask the team if the development database refuses your connection. |
| Account Pending Setup after signing in | Your Clerk user has no cloverMerchantId in its public metadata. Ask a platform admin to link you to the sandbox merchant. |
POST /api/v1/auth/session answers 401, or the browser reports CORS errors | The web app's Clerk keys must come from the instance in the API's Clerk:Authority, the web app must run on http://localhost:3000, and NEXT_PUBLIC_BACKEND_URL must point at your API. |
# Web tests on Node 25 or later, from src/web
$env:NODE_OPTIONS = "--no-experimental-webstorage"
npm test
Remove-Item Env:NODE_OPTIONS
# The two culture-sensitive API tests under NLS, from src/api
$env:DOTNET_SYSTEM_GLOBALIZATION_USENLS = "1"
dotnet test tests/CloudPayClover.Infrastructure.Tests --filter "FullyQualifiedName~Sms_TruncatesLongCustomerNames_WithAsciiEllipsis|FullyQualifiedName~SendAsync_EmailBody_OmitsCardRowForCashPayments"
Remove-Item Env:DOTNET_SYSTEM_GLOBALIZATION_USENLS