Whenever I am writing complex/transactional routines that can have grave consequences such as this (billing logic) I try to do a few things:
1. Build it out as a standalone operation/class/module so it has more ceremony around it, while also being limited in scope and easy to audit.
2. Continue to utilize the DB/ORM's filtering/querying to grab data as was done in this case
3. Additionally, when it comes down to performing the big io/side-effect, ensure additional checks are in place that confirm the data I am working on matches the query that was used to ask for the data. So in each customer loop I might double check that this customer is indeed needing an upgrade/etc. It can be slower, but it is worthwhile.
1. Build it out as a standalone operation/class/module so it has more ceremony around it, while also being limited in scope and easy to audit.
2. Continue to utilize the DB/ORM's filtering/querying to grab data as was done in this case
3. Additionally, when it comes down to performing the big io/side-effect, ensure additional checks are in place that confirm the data I am working on matches the query that was used to ask for the data. So in each customer loop I might double check that this customer is indeed needing an upgrade/etc. It can be slower, but it is worthwhile.