March 01, 2024

Get paid in QR

No, not in pixels, in euros.
No, not with credit card.
Yes with phone.

QR Payment

Motivation

If you sell product or services getting paid is an obvious requirement.
These days the credit-card is the king, well online anyway.
You have an abundance of payment gateways, that will handle credit cards, google or apple pay for you.
For a fee, of course.

In a physical store, you would get a credit-card terminal.
Some banks can sell you a much cheaper Android app that turns you phone into a CC terminal.
It is worth considering, especially if you process only a few payments a day.

These options cover almost all situations in typical consumer scenarios.
Where a person goes online or into a brick&mortar shop and buys a bunch of products or gets a haircut.

But, there is much more.

  1. What if you are a non-profit or citizen-organization that needs to process small payments.
  2. What if you are a B2B company and your customers want ot pay with direct transfers.
  3. Or what if you just don't want to pay the ~2-3% overhead for credit card payments.

In all of these cases the answer, in the European Union, is SEPA.
More specifically SEPA Credit Transfer.
Or its younger, but much faster sibling SEPA Instant Credit Transfer.

SEPA Credit Transfer

If you ar enot familiar with the name, don't worry, it is the same old same old wire-transfer.
Eg. you tell your bank to take money from your account and to put in on somebody elses.

What was historically a paper form to fill out, is these days a couple of clicks in your mobile banking app.

But, we now have standardized international bank account numbers - IBANs.
Making it super easy to send money anywhere in the EU or even world-wide.
And with SEPA instant, your money arrives in seconds.

But, but the IBANs are so long, they are almost frightful.
And people are notoriously bad at entering stuff, so you can be happy if you get the right amount, forget about additional notes.

All of these problems are easily solved with QR codes.

Your customers can scan them with their phones, check the amount and click pay in their banking app.
With the growing list of banks in the SEPA Instant Credit Transfer scheme, chances are high, your money arrives in an eyeblink.
In a future blog, we will take a look at how to watch for incoming payments on your bank account.
To automate invoice generation and sending for example.

Let me show you how to generate QR codes for Slovakia and Czech Republic.
I want to make this list longer, so if you know about how QR codes are made in your country, please let me know.

QR Slovakia

Slovak banks have agreed on a common standard called Pay by Square.

There is even a url schema that can be used in online scenarios.
Eg. the user clicks a link and their banking app is launched with all the payment information filled out.
Sadly, these are not interoperable (in 2024).

Let me show you how to generate the QR code in NodeJS.


app.get("/api/paybysquare", mustAuth, async (req, res, next) => {
	try {
		const body = req.query.content as string;
		const model = JSON.parse(body);
		const content = await generate(model);
		const qrStream = new PassThrough();
		const result = await toFileStream(qrStream, content,
			{
				type: 'png',
				width: 200,
				errorCorrectionLevel: 'H'
			}
		);

		qrStream.pipe(res);
	} catch (ex) {
		next(ex);
	}
});

The generate method is from the bysquare library.
You can install it with npm as usual npm i bysquare

To get an PNG image we can use in the browser or in a invoice document, we call the method like this.


interface IBySquareModel {
	IBAN: string;
	Amount: number;
	CurrencyCode: string; // must be "EUR",
	VariableSymbol: string;
	Payments: number; // must be 1,
	PaymentOptions: number; // must be 1,
	BankAccounts: number; // must be 1,
	PaymentNote?: string; // optional note
}

const BySquareQR = (payModel: IBySquareModel) => {
	return <img style={{ width: "120px", height: "120px" }} src={"/api/paybysquare?content=" + encodeURIComponent(JSON.stringify(payModel))} />
}

It is often very helpfull to add a descriptive message to the payment.
Here is some simple code that will normalize client names.
This will strip diacritics, turing Ján Kováč into Jan Kovac.
Should be still perfectly readable and will make sure the bank systems don't mess it up.

	const paymentMessage = customerName?.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").substring(0, 100) || "";

QR Czech Republic

Even though Czech Republic or Czechia has so far (2024) resisted the euro, they too have QR code scheme.

And they even have a royalty free code generator we can use. Now, for security reasons, you still might want to generate it yourself.

const czQrLink = "https://api.paylibo.com/paylibo/generator/image?iban=" + iban + "&amount=" + 
czkPrice + "&currency=CZK&vs=" + vsym + 
"&message=" + message;

const CzQrImage = (czQrLink: string) => {
	return <img style={{ width: "120px", height: "120px" }} src={czQrLink} />
}

International

Sadly, there is not yet an international or european QR standard.
So you'll have to to show the right QR code based on your user's country.

If you expect payments from Czech Republic, keep in mind the Czech QR codes only work in Czech crowns (czech currency CZK).
Or if you are based in Czech Republic and you expect payments from Slovakia, the QR only works in Euros.

The code below can help you calculate the right currency amount.
First we get the current reference exchange rate from the central bank.
Then we calculate the euro and czk prices, depending on the product price and currency.


const getExchangeRate = async () => {
	const now = new Date();
	const dt = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1).toISOString().substring(0, 10);

	const url = "https://nbs.sk/export/sk/exchange-rate/" + dt + "/xml";
	const dat  = { url: url }
	const opts = {
		headers: {
			'Content-Type': 'application/json'
		},
		method: "POST",
		body: JSON.stringify(dat)
	}

	const resp = await fetch("/api/httpfetch", opts);
	const xml = await resp.text();

	const parser = new DOMParser();
	const xmlDoc = parser.parseFromString(xml, "text/xml");
	const node = xmlDoc.querySelector("Cube [currency='CZK']");
	if (node) {
		const attr = node.getAttribute("rate");
		if (attr) {
			const num = attr.replace(/\s/, "").replace(",", ".");
			return +num;
		}
	}
	return undefined;
}

	// basePrice is the amount to be paid, for the products or services
	// currencyCode is the currency the products or services are sold in.
	const czkRate = await getExchangeRate();

	const eurPrice = currencyCode === "EUR" ? basePrice : (Math.ceil(basePrice / czkRate * 100) / 100).toFixed(2);
	const czkPrice = currencyCode === "CZK" ? basePrice : (Math.ceil(basePrice * czkRate * 100) / 100).toFixed(2);

Get Paid

Trying to read badly printed IBANs from invoices or even typing in the horribly long IBANs is super annoying.
That you have to be very careful and check three times, because it is money, makes it even more so.

We make software to make our lives easier, and QR codes for payments fit this goal nicely.

I hope you found the information useful, and if you have some pointers about your national QR code schemes, please shoot them my way.

Happy hacking!