NEXUS
Product Overview

What is CloudPay?

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.

Who uses it

Merchant staffUsers with the 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 adminsmerchant_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.
MiCamp platform adminsplatform_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.
The merchant's customersThey receive invoices and receipts by email or text. An invoice's Pay button opens a Clover-hosted checkout page, so customers type their card details into Clover, not CloudPay.
ProspectsThe public landing page at /home explains the product and takes demo requests, which are emailed to the sales inbox.

Where it sits

CloudPay keeps its own records of merchants, users, invoices and payments, but Clover stays the system of record for money, devices and inventory.

PieceWhat 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 databaseOne Azure SQL database per environment behind the API, managed with EF Core migrations that apply when the API starts
CloverOAuth 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
ClerkSign-in and sign-up, user search, invitation emails, and user metadata: the Clover merchant id (public) and the merchant role (private)
DMS serviceA 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 · SMTP2GOText messages and email: invoices, receipts, paid notifications and demo requests
AzureContainer 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 ClaritySession analytics, loaded only in production builds of the web app

Tech stack

AreaChoice
Web frameworkNext.js ^16.3.8 (App Router, Turbopack in development, output: "standalone"), React 19, TypeScript in strict mode
Web UITailwind CSS 3 with shadcn/ui (Radix), DM Sans, light, dark and system themes (next-themes), Recharts, lucide-react
Web dataTanStack Query 5 over plain service modules that call the API or the same-origin route handlers; react-hook-form with zod 4 for forms
AuthClerk (@clerk/nextjs 6) in the browser. The API accepts Clerk session tokens and its own short-lived JWT.
Clover SDKremote-pay-cloud 4: sales, refunds and receipt prompts on a terminal, through Clover's cloud
APIASP.NET Core Web API on .NET 10 (net10.0). Solution CloudPayClover.slnx in four layers: Api, Application, Domain, Infrastructure.
API dataEF 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 integrationsTyped 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
TestsxUnit on the API (185 test methods, run by its pipeline) and Vitest on the web app (163, not run by any pipeline)
HostingWeb: 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.

Accounts, roles and onboarding

Clerk holds the logins. The API's Users table mirrors each one with a role, a merchant and a department.

RoleWhat it unlocks
merchant_userSales, Dashboard, Preferences, and Inventory and DMS when the merchant has them
merchant_adminAll of the above, plus Settings with its Users tab. The API's MerchantAdmin policy.
platform_adminThe Admin area. The API's PlatformAdmin and AdminOnly policies; it also passes MerchantAdmin.
  1. 1
    Create the merchantA platform admin enters the business name, email, an optional Clover merchant id, and the settings, including the integration type. If there is an email, the API immediately invites it through Clerk as a merchant admin.
  2. 2
    Connect CloverOn the merchant's admin page, Connect to Clover starts OAuth with PKCE, with the merchant's id as the state. The API exchanges the code, encrypts and stores the tokens, and copies the business name and time zone from Clover.
  3. 3
    Invite the teamPlatform admins invite more people as merchant user or merchant admin, or link an existing Clerk account. Invitations need the merchant to be connected first.
  4. 4
    AcceptWhen the invitee signs up, Clerk calls the API's webhook (user.created). The API applies the invitation's role and merchant link to the Clerk account and creates the local user.
  5. 5
    Sign inThe web app's onboarding guard sends platform admins to /admin. Everyone else needs a linked merchant; the guard then opens a session with the API and loads the app.
  • Account Pending Setup: a signed-in user whose Clerk account has no Clover merchant id sees only this screen, with Sign Out, until a platform admin links them.
  • Account Deactivated and Connection Disconnected appear when the merchant has been deactivated, or its Clover connection has expired or been revoked. Merchants cannot reconnect Clover themselves: the screen tells them to contact MiCamp support, and /connect-clover is for platform admins only.
  • Users tab (merchant admins) and the admin merchant page list users with search, role and department filters, and sorting. Edit changes the role (User or Admin) and department. Nobody can change their own role, platform admins cannot be edited there, and the change applies at the user's next sign-in.
  • Departments are Parts and Service (shown as Repair Orders). They are recorded but do not restrict anything yet.

Screens

ScreenWhat it is forWho sees it
Sales (/)The Sales Terminal: send a sale to a Clover terminal or send an invoice, with Today's Transactions underneathEvery merchant user; where sign-in lands
DashboardThree tabs: Overview (totals and a chart), All Transactions, Invoice TransactionsEvery merchant user
InventoryThe merchant's Clover items, read-onlyWhen the merchant's Inventory setting is on
DMS (labelled Reynolds or DealerTrack)Repair orders and parts invoices with their payment history, and Pay NowWhen the integration type is Reynolds or DealerTrack. Only Reynolds has data.
Payment detailsEvery field of one payment, its RO or parts invoice, and receipt actionsFrom the DMS pages
SettingsMerchant Settings, Receipts & Invoices, UsersMerchant admins
PreferencesThe user's own time zone and favourite deviceEvery merchant user
AdminMerchant list, create, edit and detail pagesPlatform admins
Public pages/home, /privacy-policy, /eula, sign-in and sign-upAnyone

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.

Taking a payment

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.

  1. 1
    Pick a terminalThe page fetches the merchant's Clover token from the API and lists the merchant's devices from Clover, with the user's favourite device preselected. Connect to Device opens the cloud connection, which the whole app then shares.
  2. 2
    Enter the amountType it or use the keypad. Digits fill from the right (5 → 0.05 → 0.51), up to $999,999.99.
  3. 3
    Tax and referenceA manual sale adds the merchant's tax rate. If the merchant has turned the caption field on, the user also enters a reference, labelled with the merchant's caption name. It travels with the sale as Clover's externalReferenceId.
  4. 4
    SendSend to {device} is enabled only while the terminal is Ready. A dialog shows Processing, with Cancel Transaction.
  5. 5
    On the terminalThe customer chooses a tip on screen, then pays. Every card entry method is allowed.
  6. 6
    ResultSuccess shows Transaction Completed for 3 seconds and clears the form. If the customer cancels on the terminal, the dialog offers Retry with the same amount.
What is sent to the terminal (PaymentInput, CloverService)
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)
  • Device panel: Ready, Connected, Connecting…, Connection issue or No device connected. The star saves the connected terminal as the user's favourite. While disconnected it offers Connect to favorite and Connect to last device.
  • Reconnects: if Clover rejects the token, the app renews its API session and Clover token and reconnects once; a second rejection within 30 seconds gives up. Errors such as Cloud Pay Display not running end the connection, so the SDK stops retrying.
  • From the DMS, Pay Now opens the terminal with the amount, the RO or invoice number (locked), and a Full or Partial payment switch. Payments already recorded against the reference are subtracted. Cancel order goes back to a manual sale.
  • Signature and payment-confirmation prompts from the terminal are not handled yet.

Receipts and refunds

  • Send Receipt goes to an email, a phone number (10–15 digits) or both. The API builds it from Clover's copy of the payment or refund and links to Clover's own receipt page. The result says which channels worked.
  • Print asks the connected terminal to show its receipt options.
  • Refunds run on the terminal through the Clover SDK, never through Clover's REST API, so they need a connected, Ready terminal. Choose Full Refund, or an amount up to the payment amount without tip and tax. One refund runs at a time.
  • Refund reason: free text up to 1,000 characters. Required when the merchant has turned on Require a reason for refunds & voids.
  • Refund PIN: a 4-digit PIN asked for before a refund, when the merchant has turned it on and set one.
  • The reason is saved against the original payment's id, because the SDK's refund response never returns the new refund's id. The same key covers a refund row (through its reference payment) and a void (Clover voids an unsettled payment rather than refunding it). Lists show a note icon on rows that have a reason.
  • Refunds are offered in two places, the Dashboard's transaction detail panel and the Today's Transactions dialog. The Dashboard refuses refunds on invoice payments; the Today's Transactions dialog does not check.

Invoices (pay-by-link)

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.

  1. 1
    CreateOn the Sales Terminal's Send Invoice tab: the amount, with the same tax and reference rules as a sale, then Email and/or SMS. Phone numbers must be 10 digits. One invoice holds both contacts.
  2. 2
    SendThe API saves the invoice as Outstanding and sends the email and the text. Both carry the pay link, /api/v1/hosted-checkout/{invoiceId} on the API's host.
  3. 3
    PayOpening the link creates a Clover Hosted Checkout session for the invoice total and redirects to it. The invoice id is written into the checkout's notes. A paid, cancelled or unknown invoice gets a plain explanation page instead.
  4. 4
    MatchClover's payment webhook reaches the API, which links the payment to the invoice (see below) and marks it Paid.
  5. 5
    NotifyWhen an invoice first becomes Paid, the API emails a payment-received message to each of the merchant's notification emails. A replayed webhook does not send it again.
How a payment finds its invoice (PaymentService.TryLinkInvoiceAsync), first match wins
MatchWhen it applies
1. Invoice id in the Clover order's notesHosted Checkout payments: the line-item note (or the order note) holds the invoice's GUID
2. Reference equals the invoice's OrderIdTerminal sales whose externalReferenceId matches an Outstanding invoice
3. AmountOtherwise, the oldest Outstanding invoice whose total equals the payment amount
Invoice statuses (sent as numbers)
StatusCodeSet by
Outstanding0Create. The only status that can be paid or resent.
Paid1A captured payment linked by the webhook
Cancelled2Cancel, from the invoice list. A paid invoice cannot be cancelled.
Refunded3Refund webhooks that add up to the amount charged (amount plus tip)
PartiallyRefunded4Refund webhooks for less than that
Voided5The linked payment comes back VOIDED
  • Invoice Transactions (Dashboard): tiles for Total Outstanding, Paid This Period, Overdue (outstanding for 30 days or more), Avg. Payment Time and counts by status; status filter buttons; search over reference, email and phone; a date range; newest or oldest; pages of 10, 25 or 50.
  • Detail panel: the contacts, Created, Last Sent and Paid times, the amounts, the Clover payment id, and the payment history of the invoice's Clover order.
  • Resend (Outstanding only) goes to the contacts on file or to a different email and/or phone, which then replace them.
  • Email: the merchant's Clover logo, the amount due and a Pay button. A DMS invoice adds the department, RO or parts-invoice number, customer and advisor, and its subject becomes "{Department} Invoice #{number} from {business}".
  • Text: the business name, amount, invoice number and link. A DMS invoice adds the customer name, cut to 40 characters so the text stays within two segments.
  • The Receipts & Invoices settings tab shows only the logo for now. The display toggles exist in the settings model, but the templates do not read them yet.

Transactions and the Dashboard

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.

Where each screen's numbers come from
ScreenSource
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 TransactionsThe API's invoice list (OData) and invoice stats
DMS payment history, payment detailsThe API's Payments table, looked up by reference (externalReferenceId)
  • Overview: Today, Yesterday, Last 7 days or This month (the default), up to two months, and a source filter (All, Device, Invoice). Tiles: Total Revenue, Transaction Count, Success Rate, Failed/Voided, Total Tips, Avg. Transaction, Refunds, Outstanding Invoices. It warns when it hits the 500-row cap.
  • All Transactions: date presets or a custom range (no future dates, up to three months), status (All, Success, Refunded, Voided, Failed), newest or oldest, pages of 10, 25 or 50. The detail panel shows the payment, card, source app, order, employee, auth code and external id, with receipt and refund actions.
  • Source is the Clover app that took the payment, from Clover's app tracking.
  • Export CSV needs both a From and a To date. It fetches the whole filtered set (up to 20,000 payments and 10,000 refunds) and writes a header block, then 22 columns: date, time, type, status, amount, tip, tax, card brand, last 4, auth code, entry type, tender, employee, source app, invoice payment, order id, external reference, payment id, reference payment id, card reference (as text for Excel), device serial and note. The file is 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).

DMS: Reynolds and Reynolds

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.

TabKeyColumns
Repair Orders (Service)roNoRO 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)invoiceNoInvoice No, Customer No, Customer Name, Total Invoice, Created, Payment History
  • Turning it on: a platform admin sets the integration type to Reynolds and enters the dealer, store and area numbers, which are saved to the DMS service under the Clover merchant id. Each tab also needs its department setting (Parts or Repair Orders) on.
  • Tables are paged, sorted and filtered by the DMS service. Filters on the numbered columns go to the DMS.
  • Payment History comes from one batch request to the API per page. It shows Paid, Partially Paid or the latest attempt, and warns when the payments and the DMS amount disagree. The DMS amount is what is still due, so it shrinks as payments post.
  • Pay Now (hidden at $0, disabled when nothing is due) chooses Clover Payment or Send Invoice and opens the Sales Terminal pre-filled. The payment carries the RO or invoice number, which is how it shows up here afterwards.
  • Details opens the RO or parts invoice with its customer, advisor and dates, every linked payment (with its invoice and receipt actions), and the total, paid and balance.
  • Merchant identity: the DMS proxy takes the merchant from the signed-in session, never from the request, and forwards only allow-listed filters.
  • A merchant without DMS settings sees "This merchant isn't provisioned for Reynolds DMS data yet."
  • DealerTrack exists as an integration type and gets the menu entry, but there is no data source for it yet.

Inventory

A read-only view of the merchant's Clover items, behind the merchant's Inventory setting.

  • Item count, and for the current page, the out-of-stock and low-stock (5 or fewer) counts.
  • Search by name, sort by name or price, category pills, pages of 10, 25 or 50.
  • Cards show category, stock, name, code, price and quantity.
  • Add Item, the settings icon and the edit and delete icons do nothing yet. Items are managed in Clover.

Merchant settings

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.

SettingEffectWho changes it
Business name, time zoneShown 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 typeNon-Integrated, Reynolds or DealerTrack. Turns the DMS menu on.Platform admin (locked for merchants)
Parts / Repair Orders integrationTurns each DMS tab onBoth
Inventory enabledShows InventoryBoth
Tax rate %Added to manual sales and invoices. 0–99.99, two decimals.Both
Service feeStored but hidden in both forms since 2026-09-11 (US 2818), and not applied anywhere—
Invoice caption name, field activeShows a reference field on the Sales Terminal, labelled with the caption nameBoth
Notification emailsWho gets the payment-received email when an invoice is paidBoth
Require refund reasonMakes the refund reason mandatoryBoth
Refund PIN active, PINAsks for a 4-digit PIN before refunds. Leaving the PIN blank keeps the current one.Both
LogoCopied from Clover each time Settings opens, and used on invoice emails. Read-only.Clover
Receipt and invoice display togglesIn the model, hidden in the UI, and not read by the email templates—
  • Saving writes to CloudPay and, when the name or time zone changed, to Clover, and says which part failed if one did. Leaving the page with unsaved changes asks first.
  • Settings also has a Dashboard tab, hidden for now (bug 2801).

Preferences

  • Time zone: one of the US zones, or Automatic, which follows the business's zone and then the browser's.
  • Favourite device: any of the merchant's terminals, or none. It is preselected on the Sales Terminal and offered by the device panel.
  • Notifications are hidden (bug 2800). The settings exist in the API, but nothing sends them.
  • Theme (light, dark or system) is kept per browser, not saved to the account, even though the preferences model has a field for it.

Admin area (MiCamp)

PageWhat it does
MerchantsSearch by name or email; filter Active or Inactive and Connected or Not Connected; 20 per page. Row menu: View, Edit, Activate or Deactivate.
New merchantBusiness name (required), email, an optional Clover merchant id, and every setting. Invites the email as merchant admin.
Merchant detailStatus 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 merchantName, 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.
  • Deactivating a merchant blocks it at once: its users can still sign in, but every merchant route answers 403 and the app shows Account Deactivated.
  • Revoking the Clover connection marks the tokens revoked. The merchant sees Connection Disconnected until an admin connects again.
  • Clover tokens are refreshed when they are within 5 minutes of expiring and a request needs one, and by a background job every 5 minutes (one token per pass, for tokens within 15 minutes of expiry). A refresh token Clover refuses marks the connection revoked.
  • The Admin area has no phone menu.

Public pages

PageWhat it is
/homeThe 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 demoName, 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-policyCloudPay 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)
/eulaMiCamp 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-upClerk's forms over a background photo

Background jobs and logs

WhatHow it works
Clover token refreshTokenRefreshBackgroundService, every 5 minutes (see Admin area)
Webhook retryWebhookRetryBackgroundService, every minute: retries up to 10 unprocessed Clover webhooks, at most 5 times each
Webhook logWebhookLogs keeps every Clover webhook body, whether it was processed, the last error and the retry count
HealthGET /health on the API answers 200 while the process is up. It checks nothing else. The deploy pipeline polls it.
LogsThe API logs through ILogger to App Service. The web app's Content-Security-Policy reports go to the container's console.
AnalyticsMicrosoft Clarity in the production web app

Known issues

Found while documenting the code on 2026-10-15, against production (main at 2111029). None has been fixed yet.

  • A failed sale clears the form without saying why. Only success and a cancel on the terminal are handled. Any other failure closes the dialog and resets the amount, and the dialog's Transaction Failed state is never shown.
  • Inventory always reads Clover's sandbox, whatever the environment: /api/get-inventory hard-codes the sandbox host.
  • Overview counts refunds and voids as transactions. Transaction Count and Success Rate include refund and void rows, and the chart adds up every row, refunds and failures included.
  • Today's Transactions shows only Clover's first page of payments, with no date filter, and counts "today" in the browser's time zone. Its View All button does nothing, and its detail dialog shows a fixed "WAVIt Fee $0" row and a green status box whatever the status.
  • Date filters cut days at UTC midnight, while times are shown in the merchant's or user's zone, so the first or last hours of a day can land in the next or previous day.
  • Merchant admins can edit the tax rate. It was hidden on 2026-09-11 (US 2818) and came back, editable, with the QA fixes released on 2026-10-06. The settings form has a readOnlyRates switch meant for this page, which Settings does not pass. Confirm which is intended.
  • DealerTrack merchants get a DMS menu entry with no data ("DMS data isn't available for this integration").
  • A merchant created without its Clover merchant id sends an invitation with no merchant link. The new admin lands on Account Pending Setup until a platform admin connects Clover and links them.
  • Only one merchant can wait for a Clover connection at a time. A merchant created without a Clover id is stored with an empty one, and the column is unique, so creating a second such merchant fails with a server error.
  • Every unexpected error shows a 404 page ("Sorry, there's nothing here"): app/error.tsx is the error boundary, and there is no not-found page.
  • Edit merchant cannot clear a merchant's email.
  • Invoice pay links are GET requests with side effects. Mail clients that scan links can open them and create unused Clover checkout sessions (a TODO in HostedCheckoutController).
  • Some copy no longer matches the product. The landing page and page metadata say terminals connect over Wi-Fi or the network (they connect through Clover's cloud). "Invite your team" suggests merchants can invite (only platform admins can). /connect-clover speaks to merchants but is for admins. The EULA mentions Accept and Decline buttons that the app does not have.

Decisions made on purpose

  • Clover is the system of record. Transactions, devices, inventory, the business's time zone and its logo are read from Clover. CloudPay copies only what it needs: payments (for DMS history and invoice matching), the time zone and the logo.
  • The terminal is driven through Clover's cloud, not the local network, so any browser can reach any of the merchant's terminals.
  • Refunds go through the terminal SDK. CloudPay never issues a refund through Clover's REST API.
  • Refund reasons are keyed by the original payment, because the SDK never returns the refund's id, and a void keeps the payment's id.
  • Customers pay on Clover's Hosted Checkout page, so their card details are typed into Clover, not CloudPay.
  • One invoice for every channel. An invoice sent by email and text is one record with both contacts.
  • DMS details are frozen on the invoice. Invoices.DmsSnapshot is JSON, so emails can still show the RO and customer later, and new DMS providers can add fields without a migration.
  • Webhooks are idempotent. The unique Clover payment id turns a repeated delivery into an update, and the paid email goes out only when an invoice first becomes Paid.
  • Money is in cents for payments and dollars for invoices. Payments stores integer cents, as Clover sends them; Invoices stores decimal(18,2).
  • The API's JWT stays in memory, out of localStorage. The Clerk session token, which lasts about a minute, is fetched fresh for each call.
  • The API accepts Clerk tokens directly, routing each bearer token by its issuer, so the API's own JWT can be retired once the web app stops using it. Roles always come from CloudPay's records, never from the token.
  • Migrations run on startup, so a deploy that carries a migration changes that environment's database when the API boots.
  • The service fee is hidden, not removed (US 2818). The value still round-trips.
  • The Content-Security-Policy is report-only until its reports are clean. /api/csp-report logs the violations.