Any product that moves money ends up with three or more records of the same event: your database, the payment provider's record, and the bank statement. Those three will disagree. Not occasionally — routinely, for entirely ordinary reasons, including timing differences at day boundaries, refunds processed asynchronously, chargebacks, provider fees netted from settlements, and transactions that succeeded on one side and timed out on the other. Reconciliation is the system that explains the differences, and in most companies it is not a system at all. It is a person with a spreadsheet.
Building it properly starts with accepting that the three records are all correct from their own point of view and that reconciliation is a matching problem rather than an error-hunting one. For each settlement period you want an automated match on a shared identifier, an explanation for every unmatched item on either side, and a residual that is either zero or a small number a human is reviewing. The output is a daily statement of what matched, what did not, and why.
The identifier is what makes it possible, and it has to be designed in rather than derived later. Generate your own reference for every transaction, pass it to the provider on every call so it appears in their settlement file, and store whatever reference they return. Systems that try to match on amount and timestamp because no shared identifier exists work at low volume and become impossible at high volume, where several transactions share an amount within the same minute.
Categorise the differences rather than treating them as one pile. Timing differences resolve themselves by the next cycle and need no action beyond being recognised. Fees and taxes deducted at settlement are expected and should be modelled explicitly. Genuine breaks — money you believe you received and did not, or a customer charged for something you have no record of — are the ones needing investigation, and they are usually a small fraction. A system that cannot distinguish these three floods its operator with noise and gets ignored.
Run it daily, automatically, and alert on the exception rather than on the report. A reconciliation performed monthly means a discrepancy from the third of the month is investigated five weeks later, when logs have rotated and nobody remembers the deployment that caused it. Daily reconciliation with an alert when the unmatched value exceeds a threshold turns a forensic exercise into a morning task.
The reason to build this before you think you need it is that reconciliation is how you discover bugs you would otherwise never find. A double-charge affecting one customer in ten thousand, a refund that debited the wrong account, a webhook handler that silently dropped a class of event — none of these produce errors or complaints at first. They produce a small unexplained difference, every day, which is exactly what a reconciliation system is built to surface.