Agent Wranglers

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

CountryReferenceCheck digit
Any SEPA countryRF creditor reference, ISO 11649Modulo 97, two digits after "RF"
SwedenOCR number on bank giro and plus giroModulo 10, the Luhn method
NorwayKID number, 2 to 25 digitsModulo 10 or modulo 11, set per agreement
FinlandReference number, viitenumeroWeights 7, 3 and 1 from the right
DenmarkFIK payment ID on the +71, +73 and +75 formsModulo 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

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.