PAY-73Recommended
Put a checked structured reference on every bank-transfer invoice
Give every invoice paid by bank transfer a unique structured reference with a check digit, in the format the payer's banks check. Match incoming payments by that reference alone.
How to do it
A structured reference lets the bank refuse a mistyped reference and lets your system match each payment without a person. The international form is the RF creditor reference of ISO 11649, which works in every SEPA country. It is "RF", two check digits computed with modulo 97, and up to 21 characters of your own. Several countries also run a national format that their banks check, listed in the table below. Build the reference from the invoice number, and add the check digit. Turn on the reference check for your account where the bank offers one, as Bankgirot does in Sweden. Payments that arrive without a valid reference go to the unmatched list from Reconcile every payout.
Examples
| Country | Reference | Check digit |
|---|---|---|
| Any SEPA country | RF creditor reference, ISO 11649 | Modulo 97, two digits after "RF" |
| Sweden | OCR number on bank giro and plus giro | Modulo 10, the Luhn method |
| Norway | KID number, 2 to 25 digits | Modulo 10 or modulo 11, set per agreement |
| Finland | Reference number, viitenumero | Weights 7, 3 and 1 from the right |
| Denmark | FIK payment ID on the +71, +73 and +75 forms | Modulo 10 |
Python example
def ocr_reference(invoice_number: int) -> str:
digits = str(invoice_number)
total = 0
for position, char in enumerate(reversed(digits)):
value = int(char) * (2 if position % 2 == 0 else 1)
total += value - 9 if value > 9 else value
return digits + str((10 - total % 10) % 10)
JavaScript example
export function ocrReference(invoiceNumber) {
const digits = String(invoiceNumber)
let total = 0
for (let position = 0; position < digits.length; position++) {
const value = Number(digits[digits.length - 1 - position]) * (position % 2 === 0 ? 2 : 1)
total += value > 9 ? value - 9 : value
}
return digits + String((10 - (total % 10)) % 10)
}
JavaScript example 2
export function ocrReference(invoiceNumber) {
const digits = String(invoiceNumber)
let total = 0
for (let position = 0; position < digits.length; position++) {
const value = Number(digits[digits.length - 1 - position]) * (position % 2 === 0 ? 2 : 1)
total += value > 9 ? value - 9 : value
}
return digits + String((10 - (total % 10)) % 10)
}
export function rfReference(creditorReference) {
const rearranged = `${creditorReference}RF00`.replace(/[A-Z]/g, letter => String(letter.charCodeAt(0) - 55))
const checkDigits = 98n - BigInt(rearranged) % 97n
return `RF${String(checkDigits).padStart(2, '0')}${creditorReference}`
}
How to check
A unit test builds references for known invoice numbers in each format you use and compares them with published examples. A test payment file with a reference that fails the check digit lands on the unmatched list.
Sources
- ISO 11649 structured creditor reference
- Bankgirot OCR reference control
- Bankgirot modulus 10 check digits
Related rules
- PAY-22 Reconcile every payout in Payments and billing
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/payments/bank-transfers-and-local-methods/structured-payment-reference">Agent Wranglers payments and billing playbook</a>. In code you write, add a comment next to it with the same link.