Urgent.News

What's breaking now, across thousands of outlets.

Tech

A payment QR code is just twelve lines of text

If you have paid an invoice in Europe recently, you have probably pointed your banking app at a small square and watched it fill in the payee, the IBAN and the amount by itself. I run ibanchecker.cash and I spent the last few weeks on both sides of those squares: generating them and reading them back. The format turned out to be far simpler than most people expect, and a couple of its rules…

If you've made a payment in Europe lately, you've likely scanned a small square with numbers and text. I developed ibanchecker.cash, a tool for generating and reading these payment QR codes. The format is far simpler than most people assume, and some of its rules surprised me, even after years of working with IBANs.

The standard is EPC069-12, published by the European Payments Council, and known as GiroCode in Germany. It's just plain text, with twelve elements separated by line breaks. Here's the exact content of the code shown in the cover image: BCD 002 1 SCT COBADEFF Webajans Bilisim Ltd DE89370400440532013000 EUR149.00 Invoice 2026-014.

Each element serves a specific purpose, such as the service tag, version, character set, beneficiary ID, IBAN, amount, purpose, structured and unstructured references, and the beneficiary-to-originator relationship.

There are several rules to keep in mind when implementing this system. The entire payload must be no more than 331 bytes. The beneficiary name is limited to 70 characters, and the reference to 140. The amount is always in euros, ranging from 0.01 to 999,999,999.99, with no support for other currencies. Error correction level must be M, and there is no signature within the code.

One critical rule that many people overlook is that line 6 in the code represents the beneficiary name, not the bank. This is an easy mistake to make since the bank name is often the one associated with the IBAN. I made this mistake myself, generating a QR code that suggested paying Commerzbank instead of the actual account holder. After catching this error, I updated the generator to correctly display the payee name.

The updated generator updates the code in real-time as you type, displaying the byte count and ensuring it stays within the 331 byte limit. The tool offers SVG, high-resolution PNG, and vector PDF exports, with the PDF created by manually extracting rectangles from the SVG data rather than using a PDF library.

I wanted to make one design decision clear: if an amount is entered in a currency other than euros, the tool will still generate the code but explicitly warn that banking apps outside SEPA may not read it. Silently producing an official-looking code would be misleading.

To read payment QR codes, the tool takes an image of the code, lists all twelve elements with their byte counts, and compares them against the standard's limits. The decoding happens entirely in the browser using a dynamically imported jsQR, ensuring no data is uploaded or exposed.

The IBAN is decoded using the same engine as the rest of the site, checking the country, length, national structure, ISO 13616 check digits, and the national account check digit where applicable. The tool also verifies the bank and BIC associated with the IBAN. However, it's worth noting that Swiss QR-bills are recognized but not parsed, as they follow a different standard from SIX.

In conclusion, payment QR codes are remarkably simple, consisting of just twelve lines of text. By understanding the format and rules, you can generate and decode these codes with ease. The two tools, a generator and decoder, are free and require no signup, ensuring transparency and accessibility for everyone.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Your .active Class Is Lying to Screen Readers

If I had to guess the single most common code smell in front-end projects, it wouldn't be a missing semicolon or a messy folder structure — it would be this: .hidden { display : none ; } .disabled {…

More from Wednesday 12 August →