Money in an app has a simple rule that every backend developer has heard: never trust the client. I knew the rule. PeerPlay still broke it twice, in two different features.
PeerPlay is a Flutter and Firebase app that I build and run on my own. Developers test each other's apps in tracked 14-day rounds, and there is a small wallet for referral rewards and tester bonuses. Both of the mistakes below lived in that money layer, and neither one showed up as a visible bug. Everything looked fine on screen. That is exactly why this kind of mistake survives so long in a small app.
Mistake one: a balance anyone could edit
I found that a developer's wallet balance was writable directly from their own device, with no check on the server. The app talks to the database straight from the phone, so anyone who can write to their own record can write any field in it, balance included.
This is an easy trap in a Firebase app. The client SDK makes it pleasant to read and write data from the phone, and a common starting rule is "a user may write their own document". That rule sounds safe. It quietly means the user owns every field in that document, including the ones that represent money or status.
The balance did not matter much while nothing paid out of it. It started to matter the moment I planned a feature whose whole job was to put money into it.
Why referral payouts go through a person first
That feature was the referral program. The reward is $2, and it only reaches review after the referred developer registers their own app, gets five separate other testers with at least three qualified days of use each, and puts in three days of testing themselves.
Because of the wallet discovery, I did not credit referrals automatically. The first three payouts for any referrer are reviewed by hand. Auto-approval only kicks in after that, and a risk score can still pull a case back into manual review, for example when devices look shared or payout details are reused.
Manual review sounds old-fashioned for an app. For a solo developer it is the cheapest fraud system there is. A few minutes of looking at early payouts costs far less than discovering later that a reward was being farmed.
Mistake two: the phone paid the bonus
The second time came with PeerPlay's first paid campaign. It was for Nada, a women's health and cycle tracking app, on the Starter Pro package with 15 verified testers. It ran from September 16 to October 6, 2026. 18 testers joined, 11 completed all 14 days, and 217 tester-days were logged, all of them qualified. Google then granted Nada production access.
While that campaign was running, I rebuilt the paid-campaign logic. Paid campaigns had used per-tester rounds, and the tester bonus was paid by the phone. Now a paid campaign has one calendar for everyone, the bonus needs at least 12 qualified days out of 14, and the server pays it instead of the device.
It was the same mistake as the wallet in a new costume. The app was letting the device decide that a reward had been earned. With free test swaps it was easy to overlook. A paying customer made it impossible to ignore, because now the rules had to hold up for someone who had paid for them.
The rule I follow now
The device reports what happened. The server decides what it is worth. A phone can say "this tester opened the app today". It should never be the one that says "so add a bonus to this wallet".
In practice that means a few concrete checks for any small app with a wallet, credits or rewards. Money and status fields should not be writable by the user they belong to, so lock them down in your security rules and let only server code change them. Rewards should be computed from records of what happened, not from a number the client sends. Early payouts should be reviewed by a person until you trust the numbers. And any feature that pays out deserves a quick look at who can write each field it reads.
None of this needs a big backend. It needs you to ask one question about every field that touches money: who can write this? If the answer is "the phone", that is your next fix.