Insights30 de mayo de 20267 min read

Cash and card cannot live in different systems. Pick one rail, or get the spreadsheet back.

What we learned shipping Verda's POS for a multi-location retail group — and why phase-twoing the cash reconciliation module was the closest we came to breaking the platform.

A retail counter at end of shift — a cash drawer half-open, a card reader on top, a single till receipt curling between them.

Retail concepts that take both cash and card cannot have those rails live in different systems. We learned this the slow, embarrassing way building Verda — and the fix was structural, not cosmetic.

Verda is a POS and member-management platform we built for a European member-led retail group running forty locations. The customer takes cash. The customer takes card. The customer has been taking both for as long as the operation has existed, and any POS that doesn't take both isn't a POS — it's a hobby project.

We knew this going in. We still tried to phase-two the cash reconciliation module. Card processing was Stripe; the cash side was "we'll build that in v2." The till would handle card transactions through Stripe and write to the platform's transaction log, and cashiers would keep doing cash on a paper sheet for the first month while we caught up.

We were wrong about the first month being okay.

Why two rails is one rail too many

The cashiers used the platform for card transactions and the paper sheet for cash. At end of shift, the manager had to reconcile two surfaces — the platform's Z-report and the paper sheet — into a single deposit. This took an extra fifteen minutes per shift. Multiplied by forty locations, multiplied by two shifts a day, multiplied by every day, that's twenty hours a day of operational friction we'd shipped into the platform's first month.

Worse: the platform's inventory updates fired on card transactions and didn't fire on cash transactions. By end of week one, inventory was wrong at every location. By end of week two, the manager called us — politely, with feeling — to ask whether we'd considered the possibility that cashiers might sometimes accept cash. We had considered it. We had also assumed a month was a month.

We shipped the cash module in week three. Both rails write to the same transaction log. Both rails trigger the same inventory update. The Z-report ties out automatically across both. Operationally, the platform became usable that week. We would not have agreed to a phase-one without the cash module if we built it again.

Same rail in the data layer, separate UI

The fix wasn't deep. The transaction log is one table. Each transaction has a `payment_method` column (cash, card, comp, etc.). Inventory deduction fires on transaction insert, regardless of method. Reporting queries against the same log; the manager dashboard slices by method without needing two queries.

The cash UI does have its own affordances — drawer open prompts, change calculation, tendered-vs-due display — but those are on the cashier-facing till surface. The data layer doesn't care. By the time a cash transaction reaches the log, it's structurally identical to a card transaction.

This is the principle that generalizes. Different payment surfaces, same payment primitive. Any time you find yourself wanting to model a payment method as a different system, you have not yet found the unifying primitive.

A schema diagram showing one transaction log table with a payment_method column, branching outward to one inventory deduction trigger, one Z-report query, and one daily reconciliation job.
Two payment surfaces. One log. Reconciliation falls out of the schema for free.

What the operator actually wants

I'd love to claim the operator's brief was "build us a unified transaction log." It wasn't. The operator's brief was "end the spreadsheet." The spreadsheet existed because the cash rail wasn't in the system. The minute it was, the spreadsheet vanished, and so did the fifteen-minute end-of-shift ritual. That's the metric that actually mattered.

When an operator says "the spreadsheet," they are telling you the system has a hole. The spreadsheet is the duct tape over the hole. The solution is never a better spreadsheet. The solution is finding the hole and putting the data where the platform expects it.

When an operator says 'the spreadsheet,' they're telling you the system has a hole. Don't ship a better spreadsheet. Plug the hole.

On end-of-shift reconciliation

What we'd start with on day one

One transaction log primitive that handles every payment rail. Inventory deduction triggered by transaction insert, not by payment method. Z-report queries the log, not the rail. Cash UI affordances on the till, structural identity in the data. And don't phase-two anything an operator is going to need to not break their existing operation.

Cash and card cannot live in different systems. Pick one rail, or get the spreadsheet back.

Verda is in production across the group. We're happy to scope a multi-location POS and member system for any concept where the unit economics depend on both rails settling into the same log.