Query the database to find all dormant accounts with a balance, which haven’t been charged the fee this month.
That’s still not idempotent: the worker could crash between charging the fee and marking that it charged the fee.
(It sounds like in this case they’re moving the money between internal accounts, so could do it all in a transaction, which would make it idempotent. It’s not entirely clear from the context though.)
I want to add that a transaction is not enough. Its isolation level needs to be set to “serializable” otherwise you still end up with a race condition. Alternatively you can use a different optimistic concurrency mechanism to prevent errors.
The race condition looks like this. Assuming query and calculation take a duration of X you have a window of X where another process would see and do the exact same thing since the write of the first one did not yet go through. Therefore you need to enforce your idempotency when writing.
I admit the time window X is tiny and likely never hit. But if you make it idempotent in the first place you should do it right.