Reconciling multi-country payments. 7 chapters and a final quiz.
Close the books when your company collects payments in 10 countries. Read a camt.053, a BAI2, a CNAB 240, and a CFONB120 without picking the wrong field. Match an acquirer settlement report to the incoming transfer, choose between booking date and value date, reconcile a mobile money collection, and account for foreign exchange under IAS 21. Then automate without losing the audit trail.
Convert camt.053, BAI2, CNAB 240, and CFONB120 into a single canonical entry model without losing the sign or the currency
Match an acquirer settlement report to the incoming transfer and trace every difference to its cause
Distinguish booking date, value date, and availability date, then choose the one that sets the matching window
Reconcile a mobile money collection against three records: the platform, the callback log, and the bank credit
Chapter 1. One canonical model, two statements: camt.053 and BAI2.
A company operating in 10 countries doesn't receive 10 comparable statements. It gets ISO 20022 XML from its German bank, BAI2 from its US bank, CNAB 240 from its Brazilian bank, and CFONB120 from its French bank: four grammars for saying that an account was credited. The temptation is to build one reconciliation engine per country, and within five years that choice costs more than any other. The approach that holds up separates two layers. On one side sit parsers, one per format, which all produce the same object, a canonical entry. On the other sits an engine that knows only that canonical entry. Adding a country then means writing a parser, not reworking the business rules. Here are the fields the canonical entry must carry.
Normalized account identifier: the IBAN where one exists, otherwise the (bank code, account number) pair plus the country
Account currency, in ISO 4217, never inferred from the country
Amount in minor units, as a signed integer: the sign comes from a format-specific rule, not from the generic parser
Explicit direction (credit or debit), redundant with the sign and checked against it
Booking date and value date: two separate fields, never merged
The original transaction code, kept as is, plus an internal canonical family (incoming transfer, direct debit, fee, reject…)
Bank reference: the identifier the bank can trace if there is a dispute
Originator reference (EndToEndId, customer reference, or Nosso Número, depending on the format)
Full raw description, not truncated, not normalized
Source file hash and line number, to get back to the evidence in a single query
Format
Origin and where it's used
Physical layout
Amount format
Dates carried
camt.053
ISO 20022, a global standard offered by banks in every region
Self-describing XML tree
Decimal, currency in the Ccy attribute, direction in CdtDbtInd
BookgDt (booking) and ValDt (value)
BAI2
Bank Administration Institute; North American banks, also used in Australia
Lines of comma-separated fields ending in /
Integer with no decimal separator; the transaction type is set by the type code
Group “as-of” date; availability via the Funds Type field
CNAB 240
FEBRABAN, Brazil's bank-to-corporate file exchange standard
240-position records, grouped into batches by service
Fixed-position integer, implied decimals
Occurrence date and credit date, carried by the segment
CFONB120
CFONB, the French banking industry's statement format
120-character records, no separators
14 characters, the last of which encodes both a digit and the sign
Booking date and value date, in DDMMYY
The four statement families that feed the canonical model
🔑
The sign first, then the balance check
Each format encodes direction differently. camt.053 separates the amount from the CdtDbtInd indicator, BAI2 infers it from the type code, and CFONB120 locks it into the last character of the amount. A parser that copies an integer without applying the format's rule produces a wrong balance. The safeguard is the same everywhere: the opening balance plus the algebraic sum of the transactions must equal the closing balance. A file that doesn't balance doesn't enter the engine. Require every bank to supply a test file containing a debit, a credit, a reject, and a negative balance.
camt.053 is the ISO 20022 end-of-day statement. Its strength lies in three things that fixed-position formats can't carry: a currency attached to every amount, two separate dates, and a standardized transaction classification. Its weakness is its flexibility. Two banks can produce camt.053 files that are both valid and yet different, because each one decides what it actually structures and what it leaves as free text.
camt.053: a statement entry stripped down to the fields used for reconciliation
With `Amt` and its `Ccy` attribute, the currency belongs to the amount, not to the account. A multicurrency account carries entries in different currencies.
`CdtDbtInd` is either CRDT or DBIT. The amount itself is always positive: converting it into a signed integer is the parser's job.
`BookgDt` and `ValDt` show, in this example, booking on Friday the 10th and value on Monday the 13th. For treasury, that's a three-day gap.
`BkTxCd` holds three levels drawn from the ISO 20022 external code lists: domain PMNT, family RCDT (received credit transfer), and subfamily ESCT. This is the field that lets you route an entry without reading the description.
`AcctSvcrRef` and `EndToEndId` give the reference the bank will be able to trace and the one the originator set. The second is the real key for automated matching.
In BAI2 (Bank Administration Institute, version 2, released in 1987), a file contains groups, a group contains accounts, and an account contains transactions
# Lines starting with # are annotations; they are not in the real file.
# 01 file header: sender, receiver, date, time, ID, version 2
01,BANKUS33,ACMECORP,260710,0800,001,,,2/
# 02 group header: as-of date of the report and group currency
02,ACMECORP,BANKUS33,1,260710,,USD,/
# 03 account + balances: type code 010 = opening balance, in CENTS
03,0012345678,USD,010,15234025,,/
# 16 transaction: type code 142 = ACH credit received; funds type Z = availability unknown
16,142,1250000,Z,0004512,PAYOUT-BATCH-42,ADYEN SETTLEMENT/
# 49 account trailer: control total = sum of the 03 and 16 amounts, then line count
49,16484025,3/
# 98 group trailer: total, number of accounts, number of lines in the group
98,16484025,1,5/
# 99 file trailer: total, number of groups, number of lines in the file
99,16484025,1,7/
Scope
Value
What it means
Type code (balance)
010
Account opening balance
Type code (balance)
015
Account closing balance
Type code (balance)
045
Closing available balance; differs from the previous one if some funds are still unavailable
Type code (summary)
100 / 400
Total credits / total debits for the day
Funds Type
0 / 1 / 2
Funds available immediately, in one day, or in two or more days
Funds Type
S / D
Distributed availability: the following fields break down the tranches
Funds Type
V / Z
Explicit value date / availability not provided
The BAI2 codes a reconciliation reads first (amounts are always in minor units, with no decimal separator)
🎯 Quick question
In a BAI2 file, a type 16 record carries the amount 1250000 in a group declared in USD. What is the transaction worth?
Chapter 2. CNAB 240 and CFONB120: fixed-position files.
Brazil and France share one family of formats and nothing else: fixed-length records where only position matters. Shift one character and the rest of the line goes wrong without raising a single error. Here, the balance check from the previous chapter is the only real protection.
CNAB 240, whose version 10.11 dates from August 21, 2023, is published by FEBRABAN, the Brazilian Federation of Banks, not by the regulator. A file holds 240-position records organized into batches, one batch per service and per paying account. A single file can therefore mix a batch of transfers and a batch of boletos.
Structure of a CNAB 240 file
Record type 0
Header de arquivo (file header)
One per file: company, bank, generation date and time
➜
Record type 1
Header de lote (batch header)
Opens a batch: service type, payment method, paying account
➜
Record type 3
Detalhe (detail), split into segments
Each segment carries one facet of the same event (identification, amounts, address)
➜
Record type 5
Trailer de lote (batch trailer)
Batch totals: record count and sum of amounts
➜
Record type 9
Trailer de arquivo (file trailer)
File totals: batch count and record count
The file that matters for reconciliation is the retorno de cobrança, the return file the bank sends back for the boletos issued. Two segments carry everything an accountant is looking for. Segment T identifies the bill and its return movement code, which says whether the boleto was paid, rejected, or written off. Segment U carries the amounts.
Segment T: bill identification (Nosso Número on the bank side, Seu Número on the company side), return movement code, occurrence reason, and the bank fee (tarifa)
Segment U: amount paid, net amount credited, interest and discounts applied, occurrence date, and the actual credit date
The credit date is not the payment date: the payer pays the boleto one day, and the money reaches the account one or more days later
The amount paid is not the amount billed: late-payment interest, early-payment discounts, and the tarifa widen the gap
CFONB120: overpunch and what it means for a multi-country setup
CFONB120 lines up 120-character records: a 01 for the previous balance, 04s for transactions, 05s for supplementary details, and a 07 for the new balance. The encyclopedia guide maps them position by position. Only one point matters for a multi-country engine: the last character of the amount encodes both a digit and the sign, a legacy of punch cards. { means +0, } means −0, A through I mean +1 to +9, and J through R mean −1 to −9.
🔑
The architectural takeaway
Three amount conventions coexist in the same engine: explicit decimals in camt.053, integers in minor units in BAI2 and CNAB 240, and integers plus overpunch in CFONB120. Converting to the canonical model's signed minor units is the parser's job, never the reconciliation engine's. One unit test per format, with a positive amount, a negative amount, and a zero, is enough to prevent the most expensive class of incident.
🎯 Quick question
In a CNAB 240 collection return file (retorno de cobrança), where do you find the date on which the money from a paid boleto actually reaches the company's account?
Chapter 3. Acquirer files and their discrepancies.
The bank statement shows one line, a transfer from the acquirer, that covers thousands of transactions, refunds, chargebacks, and six families of fees. The settlement report is the document that explains how you get from one to the other. Without it, the bank line can't be substantiated, and the audit trail stops at the net amount.
Large acquirers publish the structure of these reports, starting with Adyen's settlement details report, which has 24 columns, including Gross Currency, Gross Credit (GC), Exchange Rate, Net Currency, and Net Credit (NC). Four of them isolate costs: Commission, Markup, Scheme Fees, and Interchange. At Stripe, the Payout reconciliation report exists only for accounts on automatic payouts, and the Balance report includes the automatic_payout_id and automatic_payout_effective_at columns, the latter expressed in UTC.
Column type
Adyen example
Stripe example
Use in reconciliation
Batch identifier
Batch Number
automatic_payout_id
Links the bank statement line to all the transactions it settles
Typical gap: conversion by the bank, receiving fees, reject and reissue
The six discrepancies you see everywhere
Observed discrepancy
Most common cause
Where to prove it
Report net exceeds the transfer
Periodic fees or invoice deduction taken from the batch
Fee or InvoiceDeduction lines in the same batch
A sale from that day is missing from the batch
Batch cutoff: the transaction rolls into the next batch
Transaction timestamp vs. batch cutoff time
An unexpected negative amount
Refund or chargeback tied to a sale from a previous month
Original reference carried on the reversal line
Cumulative gross doesn't match revenue
Foreign-currency sales converted at settlement
Gross currency, rate, and net currency columns
An amount withheld without explanation
Rolling reserve applied by the acquirer
Contractual reserve clause and dedicated report line
The transfer never arrives
Bank details rejected, or transfer split by currency
Payout status on the acquirer side, before booking any adjusting entry
Types of discrepancy between the settlement report and the bank statement
ℹ️
The anchor that holds the chain together
The transfer description carries the acquirer's batch identifier. Adyen's documentation shows a bank statement description in the form TX4313726XT batch 4, TestMerchant. An engine that extracts this identifier matches the transfer to the batch with no human intervention. Make that extraction a named, tested rule, not a regular expression buried in a script.
Settlement reports with a published structureAdyenStripeWorldline
🎯 Quick question
The net total of a settlement report exceeds the incoming transfer by €340, on a batch that is otherwise consistent. Which hypothesis should you check first?
Chapter 4. Value dates, time zones, and cutoffs.
Multi-country reconciliation rarely fails on amounts. It fails on dates. A single transaction carries four different dates, each correct in its own system, and an accountant who picks one at random creates month-end open items. The discipline is to name all four and decide which one sets the window.
Date
What it marks
Where to find it
What it drives
Transaction date
The moment the customer paid
Acquirer report, transaction log
Which fiscal period the revenue belongs to
Booking date
The entry on the bank account
BookgDt in camt.053, “as-of” date in BAI2, positions 35–40 in CFONB120
Matching against the general ledger
Value date
The starting point for interest calculation
ValDt in camt.053, positions 43–48 in CFONB120
Cash position and overdraft charges
Availability date
When the funds become usable
Funds Type field in BAI2, segment U credit date in CNAB 240
Day-to-day liquidity management
The four dates of a single transaction, and what each one determines
ℹ️
The value date rule doesn't apply everywhere
In the European Economic Area, Article 87 of Directive (EU) 2015/2366 requires that the value date of a credit to the payee's account be no later than the business day on which the account of the payee's payment service provider is credited. Outside the EEA, the value date is governed by the account agreement, and the gap with the booking date can run to several days. A treasury engine that assumes the European rule everywhere will get its positions wrong in the Americas and Asia.
Never match on equal dates. An identical reference makes a match; a date serves as a tolerance window, calibrated by corridor
Store the time in UTC and the original time zone separately. Adyen publishes a TimeZone column next to the creation date, and Stripe expresses automatic_payout_effective_at in UTC
Know the cutoff time of every acquirer and every bank, in its own time zone: it decides which side of a month-end an evening sale falls on
Keep a business-day calendar for each country, including local holidays. A transfer sent the day before a long weekend in India doesn't arrive on a European schedule
Recognize revenue on the transaction date, never on the settlement date, or you'll shift revenue from one fiscal year to another
Friday the 10th, 9:40 p.m. local time
The customer pays
The transaction is timestamped after the acquirer's batch cutoff. It goes into Saturday's batch.
Saturday the 11th
Batch cutoff
The settlement report is produced. The net is final, and the batch fees are listed.
Monday the 13th
Credit transfer sent
The acquirer sends the funds. The description carries the batch identifier.
Tuesday the 14th
Bank booking
BookgDt on the 14th. The entry appears on the statement and is matched against the general ledger.
Wednesday the 15th
Value date
ValDt on the 15th, outside the area covered by PSD2. Treasury counts the funds only from this date.
This example spans five calendar days and three distinct reference dates. Revenue belongs to Friday the 10th, while bank matching happens on Tuesday the 14th and treasury counts the funds on Wednesday the 15th. Three correct dates, three different uses.
🎯 Quick question
What criterion should a multi-country reconciliation engine use for its primary matching?
Chapter 5. Mobile money: three records, no bank line.
In much of sub-Saharan Africa and South Asia, as in some Central American corridors, payments don't land in a bank account. They land in an e-money account held by an operator and tied to a shortcode. The money stays there until the company triggers a transfer to its bank. No bank statement describes the day's sales.
Over $2 trillion
value processed through mobile money worldwide in 2025, up 23% year over year
GSMA, State of the Industry Report on Mobile Money 2026
593M
monthly active mobile money accounts worldwide in 2025, out of 2.3 billion registered accounts
GSMA, State of the Industry Report on Mobile Money 2026
3.1M
merchants accepting M-PESA in Kenya, for 46.41 billion transactions in the fiscal year ended March 31, 2026
Safaricom, FY26 annual results, May 2026
The operational consequence is direct: reconciliation runs across three records, not two. It compares the log of notifications received by the merchant system, the operator's platform statement, and the bank credit from the periodic transfer. All three must balance separately.
Three-way reconciliation of a mobile money payment
Customer
Pays to the merchant's shortcode
Biller-type or merchant payment account, depending on the service subscribed to
➜
Operator
Notifies the merchant system
Confirmation carrying the transaction ID, the amount, and the reference entered by the customer
➜
Merchant system
Logs and matches the order
Deduplication on the transaction ID, never on amount and time
➜
Operator
Maintains the merchant account balance
The platform statement prevails over the notification log
➜
Merchant
Triggers the transfer to the bank
A single line on the bank statement for thousands of payments
The operator's transaction ID is the unique key. The M-PESA C2B notification carries it as TransID, along with TransAmount, BusinessShortCode, and the TransTime timestamp
The order reference is entered by the customer, in the BillRefNumber field on a biller-type number. It is wrong in a significant share of cases, so fuzzy matching is mandatory
Notifications get lost and get repeated. Delivery isn't guaranteed to happen once: deduplicate on the identifier, then query the transaction status API when in doubt
The payer's number may be masked, depending on how the service is configured: don't build your matching on it
The merchant account float is a separate asset, to be tracked like a cash drawer: Mobile Money Limited, a subsidiary of MTN Ghana, reports GHS 38.4 billion in float for 2025 alone (MTN Ghana results, March 2026)
⚠️
The notification log is not a source of truth
A merchant system that knows only its own callbacks believes it has collected whatever it was notified of. A lost notification becomes an unpaid order even though the money arrived; a replayed notification becomes a duplicate payment. The operator's platform statement is the arbiter, and the only one. Download it daily, reconcile it against the log, and treat discrepancies as incidents, not noise.
🎯 Quick question
A mobile money payment notification arrives twice with the same transaction ID. What should the merchant system do?
Chapter 6. Multicurrency and FX.
Selling in 10 currencies and reporting in one requires two conversions at two different moments. The first is performed by the acquirer or the bank, at its own rate, at settlement. The second is an accounting conversion: it translates the transaction into the presentation currency of the financial statements. Confusing the two creates an FX difference that looks like an operating loss.
IAS 21, the IASB standard on the effects of changes in foreign exchange rates, sets the accounting framework. A foreign-currency transaction is recorded at the spot rate on the transaction date. At the closing date, monetary items (receivables, payables, cash) are remeasured at the closing rate. Nonmonetary items measured at historical cost stay at their original rate. The resulting differences go to profit or loss.
Step
Rate applied
Who applies it
Accounting effect
Sale to a customer in a foreign currency
Spot rate on the transaction date
The company, under IAS 21
Recognizes revenue and a foreign-currency receivable
Settlement by the acquirer
Rate published in the settlement report
The acquirer or PSP
Locks in the amount collected in the settlement currency
Period-end close
Closing rate, on monetary items only
The company, under IAS 21
FX difference in profit or loss, kept separate from revenue
The three rates for a single sale, and what they produce
⚠️
Three decimal conventions, one integer
The minor unit changes with the currency. ISO 4217 assigns two decimal places to the euro and the dollar, none to the Japanese yen and the Korean won, and three to the Kuwaiti, Bahraini, and Tunisian dinars. An engine that always divides by 100 overstates a yen amount by a factor of 100 and understates a dinar amount by a factor of 10. Load the ISO 4217 exponent table once, at startup, and use it for every conversion.
Store three values, never one: the amount in the transaction currency, the amount in the settlement currency, and the rate that links them
Never recompute a rate from the ratio of two rounded amounts: rounding hides fees and produces a false rate
Set the rounding rule once and for all, and document it. A half-cent resolved differently by two systems creates one-cent discrepancies per transaction, invisible one at a time and massive across a million
Isolate the acquirer's FX markup: derive it by comparing the report's rate with a reference rate for the same day, and negotiate it like a fee
Treat conversion imposed on the cardholder (dynamic currency conversion) as a separate line: it changes the gross amount without changing the sale
That leaves nonconvertible currencies and those subject to exchange controls, where funds collected locally can't leave the country freely. The receivable exists, and so does the collection, but repatriation doesn't. Track these balances in a dedicated account, country by country, with their aging.
🎯 Quick question
A ¥5,000 sale is collected by an acquirer that settles in euros. How should the gross amount be written in the canonical model?
Chapter 7. Automating without losing the audit trail.
Automated reconciliation fails in two opposite ways. Too cautious, it leaves a queue of open items that no one works. Too permissive, it matches lines that have nothing to do with each other and produces books that are wrong but balanced. The way out is a cascade of named rules, ordered from safest to riskiest, each one logging what it did.
Matching cascade, from most certain to least certain
Level 1
Identical end-to-end reference
`EndToEndId`, batch identifier, structured reference. Automatic match, no review
➜
Level 2
Approximate reference and exact amount
Reference truncated by the format or entered by the customer. Automatic match, with a log of the correspondence chosen
➜
Level 3
Exact amount, counterparty, and date window
Automatic below an amount threshold, human review above it
➜
Level 4
Grouping: one payout against n transactions
Proposed by the engine, approved by a person, never applied on its own
➜
Level 5
Open item
No rule made a match: the line goes to the exceptions queue, with its age
References that survive the journey
ISO 20022 `EndToEndId`, set by the originator and passed on to the payee when the chain preserves it
Swift UETR, a unique end-to-end identifier in UUID version 4 format, mandatory in field 121 of the user header for all Swift users since November 18, 2018
ISO 11649 structured creditor reference: the prefix RF, two check digits calculated with MOD 97-10, then up to 21 free-form characters. The check catches a typo before it enters the engine
Pix `EndToEndId`, 32 characters mandated by the Banco Central do Brasil: E, the agent's 8-digit ISPB, a yyyyMMddHHmm timestamp in UTC, then 11 sequence characters
Acquirer batch identifier, present both in the report and in the transfer description; it's the linchpin of the settlement level
🔑
What an automated decision must record
Every match produces a record: the ID of the rule that made the match, the cascade level, the source file hash and line number, a timestamp, and the author (robot or person). The record is kept with the supporting document, and the match can be undone without erasing it. Without it, an auditor can neither replay a reconciliation nor understand why two lines were judged identical.
The source file is never modified. It is kept exactly as received, with its cryptographic hash and date of receipt
A matching rule never writes off a difference. Creating an adjusting entry to make a discrepancy disappear is an accounting decision, subject to a threshold and separate approval
Open items age, and they get counted. An aging report by country and by acquirer, reviewed weekly, keeps the queue from becoming a graveyard
Measure the auto-match rate by corridor, not globally: a good worldwide figure always hides a country that no longer balances
Test every new rule against historical data before activating it, and measure its effect on the false-match rate, not just on the match rate
One last trade-off comes up in every project: a single engine deployed across all countries, or a tool chosen subsidiary by subsidiary. The single engine wins as soon as the same customers pay in several countries, because a refund then crosses accounting boundaries. A local tool still has its place when a country imposes its own reporting format. The two share the canonical model, which is common everywhere and specific nowhere.
🎯 Quick question
Why must the record of an automated match include the ID of the rule that made it?