Let's talk
concurrency

He paid the deposit online and the counter asked again

A customer paid the rental and a five thousand rupee deposit online. The booking then asked him for five thousand more, and the expected deposit read ten thousand.

·

A screen on a counter in a hire office; beside it the Sazinga Rentals reservation settlement tab with deposit and deduction rows.

The first real customer to pay through the new online payment page paid the rental balance and the five thousand rupee security deposit in one go. It went through cleanly. Then the booking page asked him for five thousand rupees, and the office’s settlement screen said the deposit expected on that booking had gone up to ten thousand.

Nothing about the payment was wrong. The money had arrived. What was wrong was the record of it, and the cost of that is not five thousand rupees. It is the first customer to trust the new page being asked to pay the same deposit twice, at the counter, by staff whose screen agrees with the request. A counter clerk cannot see that the system is wrong. They can only see a pending deposit.

What was actually going on

For most of this system’s life, a deposit entry in the books meant one thing: money received. The entry had to name a payment method. Writing one said cash was in hand, and the settlement figures took it off what the customer still owed.

That left the expected deposit with nowhere to live. It was always worked out on the fly, from the vehicle’s standard deposit plus an inter-state amount when the booking called for one. So a booking that needed a larger deposit than the car’s usual one had nowhere to say so. The office could not write down “we are going to ask for thirty thousand on this trip” without recording that thirty thousand had already been taken, which would have reduced what the driver was told to collect. Backwards, in exactly the direction that loses money.

The first attempt at fixing that was a pair of override fields on the booking, with a reason, an author and a timestamp. It worked. The client looked at it and asked for something better: put the expected deposit in the settlement table as an entry like everything else, and let the office mark it collected when the money arrives. So the deposit became a settlement entry that starts life as “to be collected” the moment a booking comes in, and turns into “collected” on the same entry.

Checking before building was what made this cheap: all 410 existing deposit entries in production already carried the value meaning collected in cash, so nothing already recorded changed meaning, and no data had to be migrated.

Then the online payment page was connected, and it was written to the old model. Rental balance: add a receipt. Deposit: add a deposit receipt marked as paid online. But the booking already had a deposit entry waiting, marked as not yet collected, and the payment page added a second one beside it. Two entries meant ten thousand expected. Five thousand of that was now marked collected, five thousand was still pending, and the customer was asked for the pending one.

What we changed

A deposit paid online now settles the waiting entry in place, oldest first, rather than adding a new one beside it. A part payment splits the entry so the two halves always add back to the original figure. Nothing new is added unless the money exceeds every expectation on the booking, and that case is visible rather than the default.

The principle, in plain words: when a record exists saying money is expected, a payment must resolve that record. Creating a new one is only right when the payment matches no expectation at all.

What testing found that reading would not have

The rebuild was tested end to end on a copy of production, and four defects came out of it that no amount of reading the change would have shown. All four were the same thing: a rule written for the old meaning of a deposit still enforcing itself against the new one.

The check that every deposit must name a payment method rejected a deposit nobody had collected yet, which was correct under the old rule and nonsense under the new one. The split first-payment form the counter uses vanished the moment a booking carried an expected deposit, because that form only appeared when every entry on the booking came from the rental platform. Collecting through that form created a second deposit entry instead of settling the waiting one, the same defect the online page later hit, found earlier and fixed in one place but not the other. And the expected deposit was worked out from the entries as a whole rather than separately for the security deposit and the inter-state deposit, so a booking with one seeded and not the other silently stopped asking for the inter-state amount. That one is the nastiest, because the answer it produces is a smaller number, and smaller numbers do not look like errors.

The form that asked the same question twice

Giving deposits a collected state left the entry form asking two overlapping questions: Direction, money in or money out, and “has this been collected?”. Four combinations, of which one means nothing at all, money out and not yet collected, and two say the same thing in different words.

The office found that confusing, and they were right. It was replaced with a single Status control carrying the three states a deposit actually has: to be collected at pickup, collected and held, returned to the customer. Direction is hidden for deposits and worked out from the answer. If a form offers a combination of inputs that cannot mean anything, the inputs are the wrong inputs, and the person filling it in is being made to translate.

The mechanism, and where it leaves the model

Settling in place is done under a lock on the entry, so two payments arriving together cannot both settle the same one. Deposits taken online carry their own provenance and are locked to everyone, because a settled entry otherwise still looked like the office-owned expectation it used to be, with edit and delete controls beside it. Seeded entries stay editable, so an inter-state top-up still works, as a separate entry, which is the truthful record anyway.

The rule for anyone modelling money in a workflow: decide whether each entry is a fact about what happened or a statement about what is meant to happen, make that state explicit on the entry, and make the move from one to the other the only way a fact ever gets written.

Deposits and online receipts are modelled this way in Sazinga Rentals: an expectation the office owns, settled in place by the payment that discharges it, rather than a second entry that happens to hold the same amount.

This came out of building Sazinga Rentals

Bookings, availability and the fleet standing behind them. The problem above is one we met while building it, and what we did about it is in the product.

If you run something like this, there is one thing you can do without a call: send one week's booking sheet.