WAVit Payments is MiCamp's payment app for Clover point-of-sale devices. It puts a merchant-configured front end — surcharge, tax, tips, per-tender discounts, invoices, receipts — on top of Clover's payment engine, and reports every payment to MiCamp's backend for reconciliation. The same app serves general merchants, automotive dealerships paying off repair orders from their dealer management system, and grocers taking SNAP/EBT and health-benefit cards. It is a native Android app (Java, MVVM with data binding) distributed through the Clover App Market.
One APK, configured per terminal from the server. Which of these a merchant sees depends entirely on their settings.
The app is the terminal half of the product. Most of the business logic that outlives a transaction lives in MiCamp's backend.
| Piece | What it does for the app |
|---|---|
| Clover device + Clover SDK | Card reading, authorisation, orders, tenders, printing, cash drawer, employees and app billing — all through on-device connectors. PaymentConnector is bound with the flavor's REMOTE_APP_ID. |
| MiPoint API (MiCamp backend) | The system of record. Issues the device token, serves each merchant's settings, stores every pre- and post-transaction record, invoices, inventory and orders, proxies Reynolds, and holds the WAVit Pro price baselines. Base URL TOKEN_API_BASE_URL. |
| PaymentsAgent | A second MiCamp service. Once a SignalR hub; today it is called over plain REST to relay payments between a merchant terminal and a paired customer-facing terminal. Base URL SIGNALR_AGENT_ADDRESS. |
| Configurator (MiCamp Support) | Where merchant settings are edited. The app never edits settings itself — every settings screen on the device is read-only. |
| Dealer management systems | DealerTrack OpenTrack (SOAP), CDK Fortellis (REST) and Reynolds & Reynolds (through MiPoint API). |
| Payment partners | Citcon for wallet QR payments; Fincretive FCL for SNAP/EBT and HBC/OTC benefits. |
| Observability | Firebase Analytics and Crashlytics, Microsoft Clarity session replay (Android 10+, masked by default), Better Stack Logtail, and a nightly FTP upload of the on-device log file. |
| Area | Choice |
|---|---|
| Language | Java 8 source level. The Kotlin plugin is applied but there are no Kotlin files. |
| Platform | compileSdk 31, minSdk 17, targetSdk 29 — hard-coded in app/build.gradle |
| Build | Android Gradle Plugin 7.4.2, Gradle 7.6.4, JDK 17 required to run Gradle |
| Clover | clover-android-sdk and clover-android-connector-sdk 326 |
| Architecture | MVVM with Android data binding and LiveData; one BaseActivity / BaseViewModel pair per screen |
| Networking | Retrofit 2.5 with Gson and SimpleXML converters, RxJava 2, OkHttp 3 |
| Local storage | Room 2.2.5 (a one-table payment outbox) and SharedPreferences |
| Background work | WorkManager 2.3.1 and foreground services |
| Other | JavaMail (email receipts), Glide (product images), Lottie, QRGenerator, MagTek mtscra SDK |
| Tests | JUnit 4 unit tests and Espresso instrumented tests — see Resources |
| Flavor | Application id | versionName | versionCode |
|---|---|---|---|
| production | com.wavit.prod | 5.87 | 124 |
| development | com.micamp.wavitpayments.dev | 5.96 | 145 |
AnimatedSplashActivity (the only launcher) starts Clarity on Android 10+, plays the logo animation and hands over to SplashActivity.WavItApplication.startApp() starts CardBroadcastDisableService — it swallows Clover's card-inserted and card-swiped broadcasts so Clover's own Sale app does not pop up — and schedules the nightly log upload for about 01:30.MerchantConnector returns the merchant; MerchantDevicesV2Connector returns the serial number. A serial that does not start with C stops here with "device not found".POST api/authenticate with the serial yields the device bearer token; GET api/SqlConfig returns the merchant's Settings. A failure shows a retry dialog.integrationType: fetch the Fortellis token and advisor list, load the DealerTrack service-writer or parts-counter lists, or prepare the tethered QR and payment connector. Terms & conditions are shown first if forceTermsAcceptance is on.promptForEmployeeId), optional unlock PIN, then either the amount keypad or — for a dealership — the advisor / employee ID dialog and the repair-order screen.Clover app billing is read through AppsConnector.getAppBillingInfo(). A trial shows a welcome message or a countdown; a LAPSED or INACTIVE subscription shows a blocking dialog that exits the app. The check hangs off the Clover auth callback, so it is not a strictly ordered step of the sequence above.
Two settings decide how a terminal behaves: its role, and which system it takes amounts from.
| Value | Where the amount comes from |
|---|---|
Standalone | The cashier keys it in, or builds a cart |
Tethered | A merchant terminal or external POS sends it to a customer-facing terminal |
DealerTrack | A service repair order from DealerTrack OpenTrack |
DealerTrackParts | A parts counter ticket from DealerTrack OpenTrack |
Fortellis | A CDK repair order through Fortellis |
ReynoldsService / ReynoldsParts | A Reynolds & Reynolds repair order or parts invoice, proxied by MiPoint API |
integrated and terminalType = Merchant): enters the amount and sends it to the paired customerFacingTerminal through PaymentsAgent. It skips tipping.integrated and terminalType = Customer, the default): waits for requests. Its overflow menu needs the unlock PIN.bypassIntegration on, a dealership terminal can run as standalone after the bypassPIN is entered.| Screen | What it is for | Shown when |
|---|---|---|
| Splash | Device check, token and settings, idle "tap to start", PIN, advisor ID, bypass | Always; every flow returns here |
New Card Sale (TransactionActivity) | Amount keypad in cents (max $99,999.99), invoice number, "Other information" custom fields, Add Items | Standalone and tethered |
| Inventory | Item grid with category tabs, search and a cart; side cart panel in landscape | includeInventory |
| Tip selection | Three preset tiles, custom tip, no tip | isTipAllowed, not a merchant terminal, not Pre-Auth |
Review (VerifyTransactionActivity) | Totals, tender buttons, Pay; card, cash, invoice, QR, manual refund and benefit tenders; signature; second payment | Always |
| Receipt | Thank-you screen: None, Print, Email or Text | After every payment |
| Transactions | Clover payment history with filters; refund, void, reprint, email, text, and Add Tip for pre-auths | Menu |
| Invoices | Pay-by-link invoices: list, filter, resend, cancel | Menu |
| Manual Refunds | Read-only list of Clover credits, with reprint, email and text | Menu |
| Orders | MiPoint cart orders | Menu, with includeInventory |
| Benefit Transactions | EBT and HBC order history; detail with reprint and item-level refund | Menu, with enableFincretive |
| EBT Settings | MagTek tDynamo pairing and diagnostics — not a settings page | Menu, with enableFincretive |
| Repair Orders | DealerTrack and Reynolds lookup, then pay or refund | Dealership integration types |
| Repair Orders (Fortellis) | CDK repair-order lookup, then pay | integrationType = Fortellis |
| Settings / WAVit Pro | Read-only views of the merchant's settings | Menu (WAVit Pro only with enableWavitPro) |
| Status | The last seven days of log files, with a manual upload | Menu |
| Support / About / Exit | Contact, version, quit | Menu |
Split payment, custom split and the partial-payment screen still exist in the source but nothing reaches them — their launch points and manifest entries are commented out. Partial approvals are handled on the Review screen instead (see the flows below).
The core flow. Everything else is a variation on it.
Calculation.calculateTransactionAmount(sale) to get surcharge, tax and total, and assigns a fresh payment UUID. Invoice number and required custom fields are validated first.gratuityPercent1-3 of the tip base, or flat amounts (gratuityFlat1-3) when dynamic tipping is on and the base reaches tipThresholdAmount. Custom tips are capped at 10× the total.amountDisplay = Buttons every tender button shows its own price.POST api/mitransaction to MiPoint API with the amounts, invoice, advisor, employee and custom fields and TransactionStatus 9. The returned id is kept for the post-transaction update.POST /api/orders creates the MiPoint order first; the server prices it.PaymentConnector.sale() with the total and the tip provided separately. Clover's own signature screen, receipts and printing are turned off, offline payments are refused, and the invoice number travels as externalReferenceId.disableSignature is on. The image is stored with the transaction and printed on the receipt.PUT api/mitransaction/Clover/{id} with card details and signature → Reynolds only: close the repair order → set the Clover order note to the tender caption → decrement stock per cart line → check for a partial approval.POST api/MiTransaction through the Room outbox.With transactionType = Pre-Auth the tip screen is skipped and the pre-auth is captured immediately with no tip. The tip is added later from Transactions → Add Tip, and the printed merchant copy carries a suggested-tip block with a signature line.
| Flow | How it works |
|---|---|
| Cash | The cash tender's discount applies. The app creates a Clover order with one custom line, records a cash payment on it through Clover REST, writes a cash-drawer event and opens the drawer if the tender says so. Cash skips the order note, the stock update and the partial-approval check. |
| Partial approval | If Clover approves less than requested (more than a cent short), an Amount Mismatch dialog offers to process the balance. The Review screen turns into "Review Second Payment" for the remainder, with no tax or surcharge added again. |
| Refund / void | From Transactions. Refunds can require a reason (promptForRefundReason) and a manager PIN (askManagerRefundOverride); partial refunds are allowed once the card transaction is closed. Voids use reason USER_CANCEL. |
| Tip adjust | Pre-auth rows in Transactions get Add Tip: Clover tipAdjustAuth, then PUT api/mitransaction/Clover/{id} with the tip. |
| Manual (blind) refund | The "ManualRefund" tender runs Clover manualRefund for the entered amount. The credit then appears under Manual Refunds. |
| Invoice (pay by link) | The Invoice tender forces the tip to 0, asks for a phone number or email, and posts api/Invoices/. MiPoint sends the link and the customer pays on a hosted page. Invoices can be resent or cancelled from the Invoices screen. |
| Wallet QR (Citcon) | The QR tender scans the customer's wallet code with the Clover camera and charges it through Citcon, polling for up to 90 seconds. The payment is recorded on the Clover order as the custom "WAVit" tender. |
| Inventory cart | Items and categories come from MiPoint API, 50 per page. The merchant's single tax rate and surcharge apply to the whole cart. A cart summary goes into custom field 1, and stock is decremented after card sales. |
| Tethered | A merchant terminal posts the payment to PaymentsAgent for its paired customer terminal and polls for status. An external POS on the local network can also hand an invoice number and amount to the app, which opens the tip screen. |
For dealerships. The advisor or employee ID is validated against a list loaded at startup, then the cashier looks up the document to pay.
| DMS | Lookup | Payable when | After payment |
|---|---|---|---|
| DealerTrack Service | SOAP OpenRepairOrderLookup by RO number | RO status 4, 5 or 6 | Nothing is written back to DealerTrack |
| DealerTrack Parts | SOAP CounterTicketSearch by invoice number | Always | Nothing is written back |
| CDK Fortellis | Repair order, customer and vehicle, then the amount from CDK ePayments | Status C94, C95 or C97 | Nothing is written back |
| Reynolds & Reynolds | api/Reynolds/{Service|Parts}/{id} on MiPoint API | Always | The app closes the RO or invoice (PATCH … Close) |
TransactionTypeId 14).Gated by enableFincretive, and it needs an inventory cart: the server flags which items are SNAP-eligible.
Login. Credentials come from the merchant's settings.readCardData() returns BIN, last four, expiry and the name in LAST/FIRST form. A validation swipe on the paired MagTek tDynamo follows; the flow stops with "Pair your tDynamo in EBT Settings" if it is not connected.POST /api/orders returns server-side totals and item snapshots, then FCL Authorize sends the order total including tax, with an eligibility flag per item.Worked example: an order of $15.50 plus $0.74 tax is $16.24. SNAP pays item value only, say $10.00, leaving $6.24 for card or cash. An HBC/OTC card covers value plus tax, so nothing is left. Refunds are item-level from Benefit Transactions; voiding from history is disabled on purpose.
Cash discount for merchants who ring sales up in Clover's own Register app rather than in WAVit.
The server marks the Clover catalogue up by raisedAmountPercent, so Register shows card prices. When the cashier presses Pay, WAVit Pro shows a Card / Cash overlay; choosing Cash adds one fixed order discount (caption cashPaymentDiscountCaption, default "Cash Payment Discount") so the customer pays the original cash price. Choosing Card removes any such discount.
enableWavitPro on, the order receiver is enabled and the foreground WavitProService starts, optionally after a welcome and terms dialog (requireWavitProTermsAcceptance). Acceptance is recorded on the server. A boot receiver restarts the service after a reboot or update.GET api/wavitpro/item-baselines pulls each item's cash price in cents. The terminal never writes inventory prices; the server does.discountCustomItems). Clover rejects in-place price changes, so the line is re-added at the new price and the original deleted.wavitProCashDiscountPostTax it is divided by (1 + tax rate), so the grand total drops by exactly the difference once Clover recomputes tax.The cashier still picks cash or card in Register. Choosing Cash on the overlay and then Card in Register charges the card the discounted price.
All in base/Calculation.java. Money is double in dollars throughout the app and cents only at the Clover boundary.
surcharge = sale × surchargePercent / 100
if taxRate > 0:
tax = sale × taxRate / 100
if surchargePostTax: surcharge = (sale + tax) × surchargePercent / 100
total = sale + surcharge + tax // summed from unrounded parts
each value is then rounded to 2 dp on its ownsurchargePostTax has no effect.isTipPostSurcharge, plus tax if isTipPostTax. The tip screen's "Subtotal" is sale + surcharge.discount %, or (sale + tax) × % when surchargePostTax is on.surchargeCaption.| Scenario | Surcharge | Tax | Total | Tip | Discount | Charged |
|---|---|---|---|---|---|---|
| Card, 18% tip on sale | 3.00 | 8.25 | 111.25 | 18.00 | 0 | 129.25 |
| Cash, 18% tip | 3.00 | 8.25 | 111.25 | 18.00 | 3.00 | 126.25 |
| Card, 15% tip post-tax | 3.00 | 8.25 | 111.25 | 16.24 | 0 | 127.49 |
| Card, 15% tip post-surcharge | 3.00 | 8.25 | 111.25 | 15.45 | 0 | 126.70 |
Card, surchargePostTax, no tip | 3.25 | 8.25 | 111.50 | 0 | 0 | 111.50 |
The unit tests pin these rules down as characterisation tests, including a golden case taken from production logs (sale 100, surcharge 3% pre-tax, tax 8% → subtotal 103, total 111). Several of those tests are named *_knownBug and document behaviour that is wrong but relied on — read them before changing any money code.
| Value | Meaning |
|---|---|
| 1 | Card sale (default) |
| 2 | Cash |
| 5 | Invoice |
| 12 | Refund from Transactions |
| 13 | Void from Transactions |
| 14 | Manual (blind) refund |
TransactionStatus: 9 on a pre-transaction, 1 when a tip is added.Cash, Card, Debit, Invoice, QRScan (Citcon; the tender's mid holds the store token), ManualRefund.ResponseCode "1" is success. Seen in the wild: 102 bad track name, 1009 missing void fields, 1012 unknown BIN, 413 refund-sum mismatch.Found while documenting the code on 2026-09-23. None has been fixed yet.
BaseViewModel.sendPaymentResponse casts dollars to a long and then divides by 100, so $8.25 tax is recorded as $0.08. Refund records are correct.FincretiveApi, marked TODO(prod)) and the stock-update calls after a cart sale (VerifyTransactionViewModel).termsAndConditionsAddress is ignored in favour of a fixed URL.The same pass turned up credential-handling and logging issues that need fixing before the next production release. They are tracked privately rather than on this public page.
So nobody spends time re-deriving them.
WebApiCaller cancels the in-flight request whenever a new one starts, which is why every view model chains its calls. MiPoint order create and update are exempt — cancelling them left orders stuck in Pending.CardBroadcastDisableService aborts the card-inserted and card-swiped broadcasts so presenting a card does not open Clover's own app over WAVit.