As others start discussing the next steps, Apricot gets not one but three smoothies from the fridge, quickly but carefully avoiding the ones with banana. He then flops onto a nearby beanbag chair, puts headphones on, fiddles a bit with his phone to get some music on, kicks off shoes, theatrically gurgles two of the smoothies, cracks his neck, adjusts position, downheartedly closes about twenty tabs of media more interesting than the work itself, and opens a new terminal window.
Time to get stuff done.
Start with the payment backend confirmation limits. User settings database needs a new column. The first idea is just confirm_payments_over INTEGER NOT NULL DEFAULT 50. Apricot mentally pinches himself. That will not do. Handling money is hard, any simple solution is always wrong.
What are the actual considerations about the limit, and storing it?
- Currency - It's probably enough to have a single currency and convert as needed. But it has to be user-local currency.
- Fractional denominations - Probably required for some currencies. YAGNI? Naah, better to support that immediately.
- Default value - Should we differentiate between the field having initial default value, and user setting it to that value? Seems like a good idea. That will likely mean pushing the default value from db level to application level. Fortunately having a decent abstraction layer means a single source of truth can be retained.
A quick look at postgres docs shows that the built-in money type is plagued by locale settings. Apricot dislikes anything with locale settings. DECIMAL should do the job just fine.
So maybe the columns could be confirm_payments_over_amount DECIMAL and confirm_payments_over_currency TEXT. Using TEXT instead of enum or a fixed lenght string shouldn't be too expensive. Oh but now the names look all wrong, as the _amount doesn't seem like a suffix but a part of the name. It should be (((confirm payments) over) amount) but now it reads ((confirm payments) (over amount)). That's not good.
A small bikeshedding alarm is raised and quickly silenced by Apricot's brain. This is the actual fun part, after all.
Ok perhaps the name could be payment_confirmation_threshold_amount?. He quickly googles threshold just to make sure it was typed correctly. Such a long, ugly name. How about insignificant_payment_amount? A bit better maybe, aesthetically. Less descriptive. Increaed job security not funny. Thesaurus could have some synonyms for this. Perhaps minor? Nah. Ok just the simples thing, then purchase_confirm_amount and purchase_confirm_currency. That will do.
It would be nice to have both in the same field, to make sure they're not mutated separately. Not doable on the db level, but on Rust level, sure. Actually, perhaps this whole thing could be a reusable CurrencyAmount struct. Yes. That sounds good, we'll definitely need that in other places too.
Ok what about the defaults, then? I want to show the default value to the user. I don't want to hardcode a big list of defaults for each currency. And I want to have round numbers in each currency. Perhaps take 50 USD as the base value and convert to users currency, and round it to one significant digit? That sounds good, but we cannot just recompute it each time user does a payment, as the default needs to stay the same. This is getting complicated; can we ever change the default? Users might rely on the fact that it stays the same. Oh well, I think we need to prompt the users directly about that.
Ok so new attempt. Keep the amount and currency non-nullable, and populate them on user account creation. Keep a separate boolean flag about if the user ever set the value. That works, good.
Apricot writes the backend code for it. On user creation hook, he calls a currency conversion API and rounds to a single digit precision. Surely that'll work for all values just fine, it cannot change the value more than about 5%. Then he adds an API endpoint to fetch the value. This is rather simple, just return the data directly from the db.
After thinking for a while, he adds some check constraint on the db columns. The limit value must be positive, and the currency string non-empty.
Then for the change API. Is any validation needed? Likely not more than the check constraints above. Simple SQL update statement will do. And an insert to the audit events table too, with the conversion rate as well, in case there's ever a bug or lawsuit or vague mental gestures.
Ok then the settings UI. He hates frontend, but fortunately this is rather simple. What decimal separator is used? An input with type "number" and step of 0.01 should actually handle that just as well. Does it need a reset-to-default button? Hopefully not.
It's still missing tests, but time to commit first. New branch: feature/payment-confirm-limits. Pre-commit hook complains about formatting, so cargo fmt and re-run.
Then the test, This is something that an AI agent can be trusted with. After all, he has already manually checked that it works. He prompts claude to generate a couple of tests for the backend. The AI attempts to refactor the code so that the API call to currency conversion service could be mocked. Apricot doesn't like that - it makes the code less readable. But then again, it's an external API that requires an API token to access. We should probably wrap that ourselves anyway, for caching and fallover. Ok fine, proceed with the refactor.
Then some tests for the frontend too. These are always a mess, but he plugs (or rather lets claude code to plug) a couple of lines to the E2E test suite for this.
Ok that's all. Phew. Commit, push, new pull request, prompt claude to write PR description. He waits for the CI to color itself green and clicks "request review" from Prune.
That took almost an hour. Maybe he could be done for today? He'll head to for a late lunch, at least.