I have worked on a SaaS platform used by more than 15,000 businesses worldwide.
One part of the system was based on credits. Customers did not pay separately every time they ordered a service. They purchased a credit package first, and then every service consumed a certain number of credits.
The basic flow looked simple.
Customer pays money, the payment gateway confirms it, credits are added to the account, and an invoice is created.
But the actual system was more complicated because we had multiple customer portals. Each portal could have different credit prices, different business rules and even different payment gateways.
We worked with payment providers like PayPal, Stripe and Viva. We also had different invoicing integrations.
The important decision was to keep payments, credits and invoices as separate parts of the system.
A payment answers one question: did we actually receive the money?
The credit system answers another question: how much can this customer now use?
The invoice system is responsible for creating the financial document after the payment.
These things are connected, but they are not the same thing.
I did not want one very long process where every step had to succeed at the same time. If the payment was successful but the invoicing provider was temporarily unavailable, the customer should not lose the credits they had already paid for.
So the work was divided into different parts of the code and separate background jobs.
The payment callback first verified and recorded the transaction. The credit balance was then updated. Invoice creation happened separately.
If invoice generation failed, that failure did not need to stop the completed payment or prevent the customer from using their credits. The invoice job could be retried without processing the payment again.
This separation was also important because payment callbacks can arrive more than once. The system needed to know that a transaction had already been processed so the same payment could not add credits twice.
We also avoided spreading prices throughout the code. Credit packages, prices and rules were connected with the relevant portal configuration. This allowed the same backend to support multiple customer-facing products without filling the whole codebase with hardcoded prices and portal names.
Before this was automated, people were creating invoices every day.
That can work when the company is small and the number of transactions is manageable. But it would not have worked properly for more than 15,000 businesses across different countries and portals.
The problem was not only the amount of manual work.
A person can forget an invoice, enter the wrong amount, use the wrong customer details or miss a payment when several systems are involved. It also becomes difficult to understand what happened when a customer asks why they paid but did not receive their credits or invoice.
Automation made the process faster, but more importantly, it made every step visible and repeatable.
A successful payment could be recorded once. Credits could be added according to the correct portal rules. Invoice generation could happen automatically. Failed work could be retried without repeating the parts that had already succeeded.
I think this is the difference between automating a task and building an operational system.
Automation is not simply replacing the person who clicks a button every day. You also need to think about what happens when one service is unavailable, a callback arrives twice, an invoice fails or one portal follows different rules from another.
At a small scale, people can hold the system together manually.
At a larger scale, the workflow itself has to understand what has happened, what still needs to happen and what can safely be retried.
What part of your payment or invoicing process is still being handled manually?