NEXUS
Product Overview

What is WAVit?

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.

Who uses it

One APK, configured per terminal from the server. Which of these a merchant sees depends entirely on their settings.

Retail and service merchantsKey in an amount or build a cart from inventory, take a tip, and pay by card, cash, pay-by-link invoice or wallet QR. Receipts print, email or text.
Automotive dealershipsService and parts departments take payment against a repair order or parts invoice pulled live from DealerTrack, CDK Fortellis or Reynolds & Reynolds. A service-advisor or employee ID is required.
Benefit-card merchantsGrocery and pharmacy merchants accept SNAP/EBT and health-benefit (HBC/OTC) cards through the Fincretive FCL gateway, with a MagTek tDynamo reader paired over Bluetooth.
Cash discount and dual pricingPer-tender discounts on WAVit's own payment screen, and WAVit Pro: a background add-on that brings a Cash / Card choice to the native Clover Register app.

Where it sits

The app is the terminal half of the product. Most of the business logic that outlives a transaction lives in MiCamp's backend.

PieceWhat it does for the app
Clover device + Clover SDKCard 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.
PaymentsAgentA 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 systemsDealerTrack OpenTrack (SOAP), CDK Fortellis (REST) and Reynolds & Reynolds (through MiPoint API).
Payment partnersCitcon for wallet QR payments; Fincretive FCL for SNAP/EBT and HBC/OTC benefits.
ObservabilityFirebase 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.

Tech stack

AreaChoice
LanguageJava 8 source level. The Kotlin plugin is applied but there are no Kotlin files.
PlatformcompileSdk 31, minSdk 17, targetSdk 29 — hard-coded in app/build.gradle
BuildAndroid Gradle Plugin 7.4.2, Gradle 7.6.4, JDK 17 required to run Gradle
Cloverclover-android-sdk and clover-android-connector-sdk 326
ArchitectureMVVM with Android data binding and LiveData; one BaseActivity / BaseViewModel pair per screen
NetworkingRetrofit 2.5 with Gson and SimpleXML converters, RxJava 2, OkHttp 3
Local storageRoom 2.2.5 (a one-table payment outbox) and SharedPreferences
Background workWorkManager 2.3.1 and foreground services
OtherJavaMail (email receipts), Glide (product images), Lottie, QRGenerator, MagTek mtscra SDK
TestsJUnit 4 unit tests and Espresso instrumented tests — see Resources
Current versions (root build.gradle, as of 2026-09-23)
FlavorApplication idversionNameversionCode
productioncom.wavit.prod5.87124
developmentcom.micamp.wavitpayments.dev5.96145

What happens at startup

  1. 1
    Animated splashAnimatedSplashActivity (the only launcher) starts Clarity on Android 10+, plays the logo animation and hands over to SplashActivity.
  2. 2
    Background servicesWavItApplication.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.
  3. 3
    Identify the deviceMerchantConnector returns the merchant; MerchantDevicesV2Connector returns the serial number. A serial that does not start with C stops here with "device not found".
  4. 4
    Token and settingsPOST api/authenticate with the serial yields the device bearer token; GET api/SqlConfig returns the merchant's Settings. A failure shows a retry dialog.
  5. 5
    Integration setupDepending on 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.
  6. 6
    ReadyThe external-POS listener starts, WAVit Pro is enabled or disabled from the settings, and the terminal sits on an idle full-screen "tap to start" in Clover customer mode.
  7. 7
    Tap to startOptional employee-ID prompt (standalone with promptForEmployeeId), optional unlock PIN, then either the amount keypad or — for a dealership — the advisor / employee ID dialog and the repair-order screen.

Terminal roles and integration types

Two settings decide how a terminal behaves: its role, and which system it takes amounts from.

integrationType values
ValueWhere the amount comes from
StandaloneThe cashier keys it in, or builds a cart
TetheredA merchant terminal or external POS sends it to a customer-facing terminal
DealerTrackA service repair order from DealerTrack OpenTrack
DealerTrackPartsA parts counter ticket from DealerTrack OpenTrack
FortellisA CDK repair order through Fortellis
ReynoldsService / ReynoldsPartsA Reynolds & Reynolds repair order or parts invoice, proxied by MiPoint API
  • Merchant terminal (integrated and terminalType = Merchant): enters the amount and sends it to the paired customerFacingTerminal through PaymentsAgent. It skips tipping.
  • Customer terminal (integrated and terminalType = Customer, the default): waits for requests. Its overflow menu needs the unlock PIN.
  • Bypass DMS: with bypassIntegration on, a dealership terminal can run as standalone after the bypassPIN is entered.

Screens

ScreenWhat it is forShown when
SplashDevice check, token and settings, idle "tap to start", PIN, advisor ID, bypassAlways; every flow returns here
New Card Sale (TransactionActivity)Amount keypad in cents (max $99,999.99), invoice number, "Other information" custom fields, Add ItemsStandalone and tethered
InventoryItem grid with category tabs, search and a cart; side cart panel in landscapeincludeInventory
Tip selectionThree preset tiles, custom tip, no tipisTipAllowed, not a merchant terminal, not Pre-Auth
Review (VerifyTransactionActivity)Totals, tender buttons, Pay; card, cash, invoice, QR, manual refund and benefit tenders; signature; second paymentAlways
ReceiptThank-you screen: None, Print, Email or TextAfter every payment
TransactionsClover payment history with filters; refund, void, reprint, email, text, and Add Tip for pre-authsMenu
InvoicesPay-by-link invoices: list, filter, resend, cancelMenu
Manual RefundsRead-only list of Clover credits, with reprint, email and textMenu
OrdersMiPoint cart ordersMenu, with includeInventory
Benefit TransactionsEBT and HBC order history; detail with reprint and item-level refundMenu, with enableFincretive
EBT SettingsMagTek tDynamo pairing and diagnostics — not a settings pageMenu, with enableFincretive
Repair OrdersDealerTrack and Reynolds lookup, then pay or refundDealership integration types
Repair Orders (Fortellis)CDK repair-order lookup, then payintegrationType = Fortellis
Settings / WAVit ProRead-only views of the merchant's settingsMenu (WAVit Pro only with enableWavitPro)
StatusThe last seven days of log files, with a manual uploadMenu
Support / About / ExitContact, version, quitMenu

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).

A card sale, end to end

The core flow. Everything else is a variation on it.

  1. 1
    AmountThe keypad works in cents. Next runs Calculation.calculateTransactionAmount(sale) to get surcharge, tax and total, and assigns a fresh payment UUID. Invoice number and required custom fields are validated first.
  2. 2
    TipTiles are 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.
  3. 3
    ReviewThe header shows total + tip. The first enabled tender is pre-selected; with amountDisplay = Buttons every tender button shows its own price.
  4. 4
    Pre-transactionPay sends 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.
  5. 5
    Order (carts only)With an inventory cart, POST /api/orders creates the MiPoint order first; the server prices it.
  6. 6
    Clover salePaymentConnector.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.
  7. 7
    SignatureWAVit's own signature pad, unless disableSignature is on. The image is stored with the transaction and printed on the receipt.
  8. 8
    Post-transaction chainValidate the payment through Clover REST → 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.
  9. 9
    ReceiptNone, Print, Email or Text, then back to Splash. The final payment record goes to POST api/MiTransaction through the Room outbox.

The other flows

FlowHow it works
CashThe 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 approvalIf 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 / voidFrom 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 adjustPre-auth rows in Transactions get Add Tip: Clover tipAdjustAuth, then PUT api/mitransaction/Clover/{id} with the tip.
Manual (blind) refundThe "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 cartItems 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.
TetheredA 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.

Repair-order payments

For dealerships. The advisor or employee ID is validated against a list loaded at startup, then the cashier looks up the document to pay.

DMSLookupPayable whenAfter payment
DealerTrack ServiceSOAP OpenRepairOrderLookup by RO numberRO status 4, 5 or 6Nothing is written back to DealerTrack
DealerTrack PartsSOAP CounterTicketSearch by invoice numberAlwaysNothing is written back
CDK FortellisRepair order, customer and vehicle, then the amount from CDK ePaymentsStatus C94, C95 or C97Nothing is written back
Reynolds & Reynoldsapi/Reynolds/{Service|Parts}/{id} on MiPoint APIAlwaysThe app closes the RO or invoice (PATCH … Close)
  • A positive balance runs the standard sale with the RO number as the invoice number.
  • A negative DealerTrack or Reynolds balance runs a manual refund of the absolute amount (TransactionTypeId 14).
  • A zero balance shows "Outstanding balance is 0.00" — except on Fortellis, where $0.00 can be paid.
  • The RO number, advisor ID and name travel only in the MiPoint transaction records; any posting back to DealerTrack or CDK happens on the backend.

SNAP/EBT and health-benefit cards

Gated by enableFincretive, and it needs an inventory cart: the server flags which items are SNAP-eligible.

  1. 1
    SessionOn the Review screen the app gets a Fincretive store token and runs an FCL Login. Credentials come from the merchant's settings.
  2. 2
    Card readClover 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.
  3. 3
    Order and authorisePOST /api/orders returns server-side totals and item snapshots, then FCL Authorize sends the order total including tax, with an eligibility flag per item.
  4. 4
    SNAP or HBCAn HBC/OTC processor prints an approval chit when some items were rejected. For SNAP, state-restricted items print a bilingual "Items Not Allowed" chit, then the cashier confirms "Apply $X to SNAP/EBT tender?" — No voids the authorisation.
  5. 5
    RemainderOrder total minus the benefit amount. Zero completes the order; anything left is collected by card or cash with no tax, tip or surcharge added. One combined receipt prints.

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.

WAVit Pro

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.

  • Enabling: when settings load with 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.
  • Baselines: once per service start, GET api/wavitpro/item-baselines pulls each item's cash price in cents. The terminal never writes inventory prices; the server does.
  • Custom items typed into Register are marked up the moment they are added (discountCustomItems). Clover rejects in-place price changes, so the line is re-added at the new price and the original deleted.
  • The discount is card total minus cash baseline. With wavitProCashDiscountPostTax it is divided by (1 + tax rate), so the grand total drops by exactly the difference once Clover recomputes tax.
  • Idempotent: an existing discount with the same caption is removed before the new one is added.
  • The overlay needs Android's draw-over-apps permission. Settings are edited in the Configurator; the on-device WAVit Pro screen is read-only.

How the money is calculated

All in base/Calculation.java. Money is double in dollars throughout the app and cents only at the Clover boundary.

calculateTransactionAmount(sale)
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 own
  • Tax is on the sale only; the surcharge is never taxed. With a tax rate of 0, surchargePostTax has no effect.
  • Tip base = sale, plus surcharge if isTipPostSurcharge, plus tax if isTipPostTax. The tip screen's "Subtotal" is sale + surcharge.
  • Tender discount = sale × the tender's discount %, or (sale + tax) × % when surchargePostTax is on.
  • Amount to pay = total + tip − discount. Clover receives the total and the tip separately.
  • There is no separate convenience-fee field: a convenience fee is the surcharge under a different surchargeCaption.
Worked examples: sale $100, surcharge 3%, tax 8.25%, cash discount 3%
ScenarioSurchargeTaxTotalTipDiscountCharged
Card, 18% tip on sale3.008.25111.2518.000129.25
Cash, 18% tip3.008.25111.2518.003.00126.25
Card, 15% tip post-tax3.008.25111.2516.240127.49
Card, 15% tip post-surcharge3.008.25111.2515.450126.70
Card, surchargePostTax, no tip3.258.25111.5000111.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.

Codes worth knowing

TransactionTypeId sent to MiPoint API (no enum; inferred from call sites)
ValueMeaning
1Card sale (default)
2Cash
5Invoice
12Refund from Transactions
13Void from Transactions
14Manual (blind) refund
  • TransactionStatus: 9 on a pre-transaction, 1 when a tip is added.
  • Tender types: Cash, Card, Debit, Invoice, QRScan (Citcon; the tender's mid holds the store token), ManualRefund.
  • MiPoint order status: Pending, Submitted, Authorized, Completed, Voided, Refunded, Failed.
  • FCL ResponseCode "1" is success. Seen in the wild: 102 bad track name, 1009 missing void fields, 1012 unknown BIN, 413 refund-sum mismatch.

Known issues

Found while documenting the code on 2026-09-23. None has been fixed yet.

  • Tax and tip are under-reported in the final payment record. 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.
  • Two integrations are wired to development hosts in production builds: the Fincretive gateway (FincretiveApi, marked TODO(prod)) and the stock-update calls after a cart sale (VerifyTransactionViewModel).
  • The Room outbox uses destructive migration. The declared migrations are never registered, so any schema version bump deletes unsent payment records.
  • `PostTransactionWorker` drops queued updates on any HTTP response, including 4xx and 5xx, and drains only one item per run. Workers return success before their request finishes, so WorkManager never retries.
  • A stale repair order can be paid: after a failed second lookup, the previous RO's card and Next button stay on screen (DealerTrack and Fortellis).
  • The cart is cleared before payment, so a cancelled payment loses it.
  • Refund amounts are parsed by deleting separators: a typed "12" refunds $0.12.
  • Tax may be applied twice to DMS amounts if a dealership merchant has a tax rate configured.
  • Citcon QR refunds and voids do nothing — the refund call is never made.
  • Terms-and-conditions acceptance is not stored and termsAndConditionsAddress is ignored in favour of a fixed URL.

Decisions made on purpose

So nobody spends time re-deriving them.

  • APKs are v1 (jar) signed only. Clover requires it; v2 stays off.
  • One API call at a time. 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.
  • Clover's Sale app is suppressed. CardBroadcastDisableService aborts the card-inserted and card-swiped broadcasts so presenting a card does not open Clover's own app over WAVit.
  • The terminal is read-only for inventory prices. On-device bulk inflation caused a write storm across terminals, so the server owns price writes and the terminal only reads baselines.
  • Voiding a benefit order from history is disabled. Refunds are item-level through FCL instead.
  • Signatures are WAVit's, not Clover's. Clover's signature screen is turned off so the signature can be stored with the MiCamp transaction and printed on WAVit's receipt.
  • Settings are never edited on the device. The Configurator is the single place to change them; the on-device screens only display them.