18px
FinTech·April 10, 2026·9 min read

How Payment Reconciliation Systems Actually Work Behind the Scenes

Most payment bugs show up after Pay: internal records, PSP files, and bank settlements must agree. Reconciliation is the boring backbone — normalize, dedupe, match on identifier sets, and route exceptions to ops.

How Payment Reconciliation Systems Actually Work Behind the Scenes

Most payment bugs do not happen when the user taps Pay.

They happen later.

The app says the payment succeeded. The PSP has one record. The bank has another. Your internal system has its own event. Now someone has to answer the uncomfortable question:

Did the money actually settle correctly?

That is what reconciliation systems exist for.

I went through a small Go codebase that models this flow well. It does not try to be flashy. It does the important things:

  • stores internal payments

  • ingests PSP and bank files

  • normalizes records into one format

  • runs matching logic across all three systems

  • creates exceptions for anything that does not line up

This is the part of fintech that users never see, but operations teams live inside all day.

The Real Problem

  • your internal payment record

  • the PSP settlement record

  • the bank settlement record

If all three agree, life is easy.

If one is missing, delayed, duplicated, or inconsistent, someone has to investigate before money goes out of balance.

That is why reconciliation is not a reporting feature. It is a correctness system.

What This Codebase Is Building

  • create internal payments

  • upload settlement files

  • poll files from SFTP

  • start a reconciliation run

  • fetch results, exceptions, and daily summaries

The shape is practical.

First, internal transactions are written into internal_payments.

Then external files from PSPs and banks are ingested into file_ingests, stored on disk, parsed, and converted into normalized_records.

After that, a reconciliation run compares internal rows, PSP rows, and bank rows for a given provider and date range.

That comparison produces two outputs:

  • reconciliation results
  • reconciliation exceptions

That is the core loop.

Why Normalization Matters More Than Most People Think

  • merchant_id

  • payment_id

  • merchant_order_id

  • gateway_txn_id

  • provider_txn_id

  • bank_ref

  • rrn_or_utr

  • amount_minor

  • payment_status

  • settlement_status

  • event_time

  • settlement_date

Once everything looks the same, matching becomes possible.

Without normalization, reconciliation logic turns into a mess of one-off parsers and special cases.

A Small Detail That Saves Real Trouble: File Idempotency

  • someone uploads the same file again manually

  • an SFTP poll picks up a file twice

  • an operator is unsure whether the first import worked

If you do not make file ingestion idempotent, your reconciliation results become noisy very quickly.

How the Matching Logic Works

  • internal payments

  • PSP normalized records

  • bank normalized records

Then it builds strong keys from transaction identifiers plus amount_minor.

For PSP matching, the code uses:

provider_txn_id + gateway_txn_id + merchant_order_id + amount_minor

For bank matching, it uses bank-side references:

rrn_or_utr + bank_ref + merchant_order_id + amount_minor

This is a good mental model for reconciliation systems:

you do not match on one ID, you match on a confidence set of identifiers

because no single external field is reliable enough all the time.

What a Reconciliation Run Produces

  • matched

  • partially_matched

  • manual_review_required

  • missing_internal

That maps nicely to how operations teams think.

1. Matched

2. Partially Matched

3. Manual Review Required

4. Missing Internal

The Demo Data Explains the Whole Story

Why `amount_minor` Is the Right Choice

Exceptions Are a First-Class Output

A Clean Mental Model

  • Internal payments enter the system.

  • PSP and bank files arrive through upload or SFTP.

  • Files are deduplicated by checksum.

  • Rows are normalized into one schema.

  • A run compares internal, PSP, and bank records using strong keys.

  • Matches are stored.

  • Mismatches become exceptions for manual or automated follow-up.

That is reconciliation.

Not glamorous.

But absolutely necessary.

Final Thought

Filed under fieldnotesApril 10, 2026