Payments that do not reconcile
The checkout works. The dashboard and the bank account disagree. Somewhere between a retried webhook, a partial refund and a manual export, the numbers stopped matching.
Almost every payment problem is an event problem. A charge succeeds but the webhook is delivered twice, a renewal fails and is retried three days later, a refund is issued in the provider dashboard and never reaches your database. Each of those is a row that exists on one side and not the other, and the month-end is where they all surface at once.
Make every money-moving call idempotent
A payment API that retries is not a bug; a handler that charges twice on a retry is. Every create, capture and refund should carry a key derived from the business event, so the second delivery is recognised as the same request and answered with the same result. This is the single highest-value change in most billing integrations, and it is invisible until the day it saves you.
Treat the provider as a source of truth, not the only one
Your database is the record of what your product did. The provider is the record of what the money did. Neither is allowed to be silently authoritative: a nightly reconciliation pulls both, matches on the payment identifier, and writes down why each row matched. Anything unmatched goes to a queue a person can work, with the raw provider payload attached.
Keep a ledger, not just a column
A balance that is edited in place cannot answer "why is this number what it is". A double-entry ledger can: every change is an entry with a source event, an account and a timestamp, and the balance is a sum. That costs a little more to build and removes an entire class of support ticket — the one where somebody has to open a database console to explain a customer’s invoice.
Automate the close, not just the checkout
The work does not end when the payment succeeds. Invoices, payout batches, fee entries and refunds all have to land in the books. Scheduling that reconciliation, generating the report from reconciled data and escalating exceptions is the difference between a finance team that closes in a day and one that closes in a week.
