CloudPay is MiCamp's web app for merchants who take payments on Clover devices. Staff send a sale from the browser to a Clover terminal, email or text pay-by-link invoices, follow payments and refunds, and browse their Clover inventory. Car dealerships on Reynolds and Reynolds also look up their repair orders and parts invoices and take payment against them. CloudPay is two apps in one repository, a Next.js 16 web app and an ASP.NET Core API on .NET 10, both hosted on Azure. Production went live on 2026-09-11; the latest release, on 2026-10-14, added the privacy policy and EULA.
merchant_user role. They use the Sales Terminal, the Dashboard, Inventory, the DMS pages and their own Preferences. Refunds and invoices are open to every role.merchant_admin adds Settings: the business profile, tax rate, invoice and refund rules, and a Users tab for changing a colleague's role and department. Merchant admins cannot invite people; MiCamp does that.platform_admin users get the Admin area instead of the merchant app. They create merchants, connect each one to Clover, invite and link users, and activate or deactivate accounts./home explains the product and takes demo requests, which are emailed to the sales inbox.CloudPay keeps its own records of merchants, users, invoices and payments, but Clover stays the system of record for money, devices and inventory.
| Piece | What it does for CloudPay |
|---|---|
Web app (src/web) | Next.js 16 with the App Router: every merchant, admin and public page. It has no database. Its own /api/* route handlers call Clover's REST API for live transactions, devices and inventory, and proxy the DMS service so the DMS credentials stay on the server. |
API (src/api) | ASP.NET Core on .NET 10, solution CloudPayClover. It owns merchants, users, invitations, invoices, refund notes, the Clover OAuth tokens and a copy of each payment written by Clover's webhooks. It sends every email and text. |
| SQL Server database | One Azure SQL database per environment behind the API, managed with EF Core migrations that apply when the API starts |
| Clover | OAuth v2 per merchant, the REST API (v3), Cloud Pay Display (the remote-pay-cloud SDK the browser uses to drive a terminal), Hosted Checkout for invoices, and webhooks for payments and refunds |
| Clerk | Sign-in and sign-up, user search, invitation emails, and user metadata: the Clover merchant id (public) and the merchant role (private) |
| DMS service | A separate MiCamp service that serves Reynolds and Reynolds repair orders and parts invoices. The web app calls it with client credentials, and platform admins store each merchant's Reynolds dealer, store and area numbers in it. |
| Twilio · SMTP2GO | Text messages and email: invoices, receipts, paid notifications and demo requests |
| Azure | Container Apps for the web image, App Service (Windows) for the API, a container registry, and Key Vault for the API's secrets. Azure Pipelines deploys both. |
| Microsoft Clarity | Session analytics, loaded only in production builds of the web app |
| Area | Choice |
|---|---|
| Web framework | Next.js ^16.3.8 (App Router, Turbopack in development, output: "standalone"), React 19, TypeScript in strict mode |
| Web UI | Tailwind CSS 3 with shadcn/ui (Radix), DM Sans, light, dark and system themes (next-themes), Recharts, lucide-react |
| Web data | TanStack Query 5 over plain service modules that call the API or the same-origin route handlers; react-hook-form with zod 4 for forms |
| Auth | Clerk (@clerk/nextjs 6) in the browser. The API accepts Clerk session tokens and its own short-lived JWT. |
| Clover SDK | remote-pay-cloud 4: sales, refunds and receipt prompts on a terminal, through Clover's cloud |
| API | ASP.NET Core Web API on .NET 10 (net10.0). Solution CloudPayClover.slnx in four layers: Api, Application, Domain, Infrastructure. |
| API data | EF Core 10 on SQL Server, ASP.NET Core OData 9 for the invoice list, Data Protection keys kept in the database (they encrypt the Clover tokens), and Redis or an in-memory cache |
| API integrations | Typed HttpClients for Clover and Clerk with a Polly retry (3 tries, exponential back-off), the Twilio SDK, SMTP for SMTP2GO, Svix verification for Clerk webhooks |
| Tests | xUnit on the API (185 test methods, run by its pipeline) and Vitest on the web app (163, not run by any pipeline) |
| Hosting | Web: a Docker image (Node 20 Alpine, non-root) on Azure Container Apps. API: a zip package on Windows App Service. One environment for development, one for production. |
Clerk holds the logins. The API's Users table mirrors each one with a role, a merchant and a department.
| Role | What it unlocks |
|---|---|
merchant_user | Sales, Dashboard, Preferences, and Inventory and DMS when the merchant has them |
merchant_admin | All of the above, plus Settings with its Users tab. The API's MerchantAdmin policy. |
platform_admin | The Admin area. The API's PlatformAdmin and AdminOnly policies; it also passes MerchantAdmin. |
state. The API exchanges the code, encrypts and stores the tokens, and copies the business name and time zone from Clover.user.created). The API applies the invitation's role and merchant link to the Clerk account and creates the local user./admin. Everyone else needs a linked merchant; the guard then opens a session with the API and loads the app./connect-clover is for platform admins only.| Screen | What it is for | Who sees it |
|---|---|---|
Sales (/) | The Sales Terminal: send a sale to a Clover terminal or send an invoice, with Today's Transactions underneath | Every merchant user; where sign-in lands |
| Dashboard | Three tabs: Overview (totals and a chart), All Transactions, Invoice Transactions | Every merchant user |
| Inventory | The merchant's Clover items, read-only | When the merchant's Inventory setting is on |
| DMS (labelled Reynolds or DealerTrack) | Repair orders and parts invoices with their payment history, and Pay Now | When the integration type is Reynolds or DealerTrack. Only Reynolds has data. |
| Payment details | Every field of one payment, its RO or parts invoice, and receipt actions | From the DMS pages |
| Settings | Merchant Settings, Receipts & Invoices, Users | Merchant admins |
| Preferences | The user's own time zone and favourite device | Every merchant user |
| Admin | Merchant list, create, edit and detail pages | Platform admins |
| Public pages | /home, /privacy-policy, /eula, sign-in and sign-up | Anyone |
The sidebar holds the navigation, a device status panel that can be dragged to the top, middle or bottom, the Clerk user menu and the theme toggle. On a phone it becomes a slide-out menu.
The Sales Terminal drives a real Clover terminal from the browser through Clover's cloud (Cloud Pay Display). The browser and the terminal do not need to be on the same network.
externalReferenceId.subtotal = amount entered, in cents
tax = round(subtotal × TaxRate / 100) // manual sales
tax = 0 // DMS hand-offs: the DMS total already includes tax
amount = subtotal + tax // taxAmount is sent alongside
tip = chosen by the customer on the terminal (ON_SCREEN_BEFORE_PAYMENT)An invoice is a payment request sent by email, text or both. Its link opens a Clover-hosted checkout page, and the API matches the payment back to the invoice when Clover's webhook arrives.
Outstanding and sends the email and the text. Both carry the pay link, /api/v1/hosted-checkout/{invoiceId} on the API's host.Paid.| Match | When it applies |
|---|---|
| 1. Invoice id in the Clover order's notes | Hosted Checkout payments: the line-item note (or the order note) holds the invoice's GUID |
2. Reference equals the invoice's OrderId | Terminal sales whose externalReferenceId matches an Outstanding invoice |
| 3. Amount | Otherwise, the oldest Outstanding invoice whose total equals the payment amount |
| Status | Code | Set by |
|---|---|---|
Outstanding | 0 | Create. The only status that can be paid or resent. |
Paid | 1 | A captured payment linked by the webhook |
Cancelled | 2 | Cancel, from the invoice list. A paid invoice cannot be cancelled. |
Refunded | 3 | Refund webhooks that add up to the amount charged (amount plus tip) |
PartiallyRefunded | 4 | Refund webhooks for less than that |
Voided | 5 | The linked payment comes back VOIDED |
The transaction screens read Clover live, through the web app's route handlers. The API keeps its own copy of payments from webhooks, which the DMS pages, payment details and invoice matching use.
| Screen | Source |
|---|---|
| Today's Transactions (Sales) | /api/get-payments, polled every 10 seconds: Clover's latest payments plus voids and refunds, filtered to today in the browser |
| Dashboard → Overview | /api/get-all-payments and /api/get-refunds, up to 500 rows each, plus the API's invoice stats |
| Dashboard → All Transactions | /api/get-all-payments: payments, voids and refunds merged and sorted. Without dates it covers the last 90 days. |
| Dashboard → Invoice Transactions | The API's invoice list (OData) and invoice stats |
| DMS payment history, payment details | The API's Payments table, looked up by reference (externalReferenceId) |
transactions_{merchant}_{from}_to_{to}.csv.Time zones: times are shown in the user's zone from Preferences, else the merchant's Clover zone, else the browser's. The merchant's zone is copied from Clover when it connects. Date filters, though, cut days at UTC midnight (see Known issues).
For car dealerships. The dealer management system (DMS) holds the dealership's repair orders and parts invoices; CloudPay looks them up through MiCamp's DMS service and takes payment against them.
| Tab | Key | Columns |
|---|---|---|
| Repair Orders (Service) | roNo | RO No, Cust No, Cust Name, Adv No, Adv Name, Cust RO Total Amt, Transaction Type (Payment, Invoice, Close), Created, Payment History |
| Parts Orders (parts invoices) | invoiceNo | Invoice No, Customer No, Customer Name, Total Invoice, Created, Payment History |
A read-only view of the merchant's Clover items, behind the merchant's Inventory setting.
Stored as one JSON document on the merchant (Merchants.Settings). Merchant admins edit most of it under Settings; platform admins edit all of it from the Admin area.
| Setting | Effect | Who changes it |
|---|---|---|
| Business name, time zone | Shown across the app; the zone is every user's default. Saving also updates them in Clover, and Sync from Clover pulls them back. | Merchant admin |
| Integration type | Non-Integrated, Reynolds or DealerTrack. Turns the DMS menu on. | Platform admin (locked for merchants) |
| Parts / Repair Orders integration | Turns each DMS tab on | Both |
| Inventory enabled | Shows Inventory | Both |
| Tax rate % | Added to manual sales and invoices. 0–99.99, two decimals. | Both |
| Service fee | Stored but hidden in both forms since 2026-09-11 (US 2818), and not applied anywhere | — |
| Invoice caption name, field active | Shows a reference field on the Sales Terminal, labelled with the caption name | Both |
| Notification emails | Who gets the payment-received email when an invoice is paid | Both |
| Require refund reason | Makes the refund reason mandatory | Both |
| Refund PIN active, PIN | Asks for a 4-digit PIN before refunds. Leaving the PIN blank keeps the current one. | Both |
| Logo | Copied from Clover each time Settings opens, and used on invoice emails. Read-only. | Clover |
| Receipt and invoice display toggles | In the model, hidden in the UI, and not read by the email templates | — |
| Page | What it does |
|---|---|
| Merchants | Search by name or email; filter Active or Inactive and Connected or Not Connected; 20 per page. Row menu: View, Edit, Activate or Deactivate. |
| New merchant | Business name (required), email, an optional Clover merchant id, and every setting. Invites the email as merchant admin. |
| Merchant detail | Status and connection badges; the Clover connection (last refreshed, Revoke Connection, or Connect to Clover); the users table with Edit; Invite New User and the pending invitations, which can be revoked; Link Existing User (Clerk search, 2 or more characters) and Unlink; a summary of the settings. |
| Edit merchant | Name, email, Active, the settings, and for Reynolds the DMS dealer, store and area numbers (all three or none). The Clover merchant id is set only at creation or by connecting. |
| Page | What it is |
|---|---|
/home | The landing page: hero, features, Clover merchants and dealerships, the DMS story (Reynolds today), security, and Request a demo. Light and dark, with masked product screenshots and Open Graph metadata. |
| Request a demo | Name, work email, company, optional phone and a message of 10 to 2,000 characters. The API emails it to the sales inbox. A hidden honeypot field drops bots silently, and each IP address may send 5 requests per 10 minutes. |
/privacy-policy | CloudPay Privacy Policy, effective 2026-10-14: what is collected, how it is used and shared (Clover/Fiserv, Clerk, Twilio, SMTP2GO, Azure, the DMS provider), retention, rights, CCPA, children, and SMS terms (transactional only, STOP and HELP) |
/eula | MiCamp Solutions End-User License Agreement, effective 2026-10-14: the licence, fees billed through the Clover App Market, a month-to-month term, suspension, data, liability, privacy law, New York law |
| Pay link (API) | /api/v1/hosted-checkout/{invoiceId} redirects to Clover's checkout, or explains that the invoice is already paid (200), no longer available (404) or failing (500) |
/sign-in, /sign-up | Clerk's forms over a background photo |
| What | How it works |
|---|---|
| Clover token refresh | TokenRefreshBackgroundService, every 5 minutes (see Admin area) |
| Webhook retry | WebhookRetryBackgroundService, every minute: retries up to 10 unprocessed Clover webhooks, at most 5 times each |
| Webhook log | WebhookLogs keeps every Clover webhook body, whether it was processed, the last error and the retry count |
| Health | GET /health on the API answers 200 while the process is up. It checks nothing else. The deploy pipeline polls it. |
| Logs | The API logs through ILogger to App Service. The web app's Content-Security-Policy reports go to the container's console. |
| Analytics | Microsoft Clarity in the production web app |
Found while documenting the code on 2026-10-15, against production (main at 2111029). None has been fixed yet.
/api/get-inventory hard-codes the sandbox host.readOnlyRates switch meant for this page, which Settings does not pass. Confirm which is intended.app/error.tsx is the error boundary, and there is no not-found page.HostedCheckoutController)./connect-clover speaks to merchants but is for admins. The EULA mentions Accept and Decline buttons that the app does not have.Documenting the code also turned up authorization, credential-handling and payment-security issues in both apps. They need fixing and are tracked privately rather than on this public page.
Invoices.DmsSnapshot is JSON, so emails can still show the RO and customer later, and new DMS providers can add fields without a migration.Payments stores integer cents, as Clover sends them; Invoices stores decimal(18,2).localStorage. The Clerk session token, which lasts about a minute, is fetched fresh for each call./api/csp-report logs the violations.