Creating a QR-Bill in PHP
The QR-bill aims to modernize the swiss payment transactions. It got introduced on June, 30th 2020. A QR-bill provides, as the name suggest, a QR code with all necessary payment information encoded.
In this example bellow we want to show you a basic template that implements the QR-code. You are free to use the template.
To get started, make sure you have typeset.sh required using composer. If you have a Symfony or Laravel application you can require the typeset.sh Symfony bundle or Laravel wrapper. Those will give you an easy way of rendering using the twig and blade templates.
Also make sure to require the QR-code extension. This will give you access to a QR-Code element that generates a QR code on the fly.
The QR-slip must be the last element on the bottom of the last page. Therefore we must always make sure that the last page has enough space for rendering the QR-slip. This is done by proving a placeholder box that will flow as any other content. If then the placeholder box does not fit, it makes sure a new page is insert where the QR-Codes has enough space.
The slip is not rendered inside that box, but positioned absolute to the page and placed at the bottom. The placeholder stays empty, it is just there to make sure we always have enough space.
The QR-slip can be easily layout using a grid container. Bellow is the CSS example for the receipt (right) and payment part (left). If you ever find your self not sure about the layout, it helps giving each grid item a different background for testing.
Here is the CSS used for the QR-slip part.
/*
* The payment part is in DIN-A6 landscape format (148 x 105 mm).
* The receipt to the left of the payment part measures 62 x 105 mm,
* so that the two together measure 210 x 105 mm (DIN long).
*/
.qr-bill {
/* the placeholder, that makes sure we have at least 105mm on the current page */
height: 105mm;
break-inside: avoid;
}
.qr-bill > .slip {
position: absolute;
bottom: -20mm; /* Match your page bottom margin */
left:-20mm; /* Match your page left margin */
height: 105mm;
width: 210mm;
border-top: 0.25mm dashed black;
display: grid;
grid-template-columns: 62mm 148mm;
grid-auto-rows: 105mm;
}
.qr-bill > .slip .receipt {
margin: 0 0 0 5mm;
height: 100%;
display: grid;
border-right: 0.25mm dashed black;
grid-template: 'margin' 5mm
'title ' 10mm
'info ' 1fr
'amount' 15mm
'acceptance' 20mm
'margin' 5mm
/ 1fr 5mm;
}
.qr-bill > .slip .payment {
margin: 5mm;
display: grid;
height: 100mm;
grid-template: 'title info' 10mm
'qrcode info' 55mm
'amount info' 1fr
'further further' 10mm
/ 50mm 1fr;
grid-auto-flow: row;
}
.qr-bill section {
margin: 0;
}
.qr-bill section.title {
grid-area: title;
font-size: 11pt;
font-weight: bold;
text-align: left;
}
.qr-bill > .slip .receipt section.title {
margin-top: 0;
}
.qr-bill section.qrcode {
grid-area: qrcode;
margin: 5mm 5mm 5mm 0;
position: relative;
}
/**
* 5.4.2 Recognition symbol
* To increase the recognizability and differentiation for users, the Swiss QR Code created
* for printout is overlaid with a Swiss cross logo measuring 7 x 7 mm.
*/
.qr-bill section.qrcode:after {
display: block;
position: absolute;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKYAAACmAQAAAAB488naAAAARklEQVRIx2P4jwX8YRiBoh8Y0IH9qOjwFOUHxviBUdFR0SEoyo9Uah0YFR0VpZHoYEjro6KjopSIIoFR0VFR6otiASNQFACdq/PI0UugMQAAAABJRU5ErkJggg==')
no-repeat center / 7mm 7mm;
width: 45mm;
height: 45mm;
top: 0;
left: 0;
content: '';
}
.qr-bill section.amount {
grid-area: amount;
}
.qr-bill table.amount th {
text-align: left;
font-weight: bold;
font-size: 7pt;
}
.qr-bill table.amount td {
text-align: left;
font-weight: normal;
font-size: 11pt;
}
.qr-bill table.amount .currency {
padding-right: 4mm;
}
.qr-bill section.information {
grid-area: info;
}
.qr-bill section.further-information {
grid-area: further;
}
.qr-bill section.information dl {
padding: 0;
margin: 0;
}
.qr-bill section.information dt {
font-size: 7pt;
font-weight: bold;
padding: 0;
margin: 0;
}
.qr-bill section.information dd {
font-size: 9pt;
padding: 0;
margin: 0 0 1em 0;
white-space: pre-line;
}
.qr-bill .payment section.information dt {
font-size: 8pt;
}
.qr-bill .payment section.information dd {
font-size: 10pt;
}
.qr-bill section.acceptance-point {
grid-area: acceptance;
}
.qr-bill section.acceptance-point .label {
text-align: right;
font-weight: bold;
font-size: 7pt;
}
/*
* Scissor icons
*/
.qr-bill > .slip::before, .qr-bill > .slip::after {
display: block;
position: absolute;
width: 6mm;
height: 4mm;
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJoLTYgdy02IiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHN0cm9rZT0iY3VycmVudENvbG9yIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj4KCTxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMTQuMTIxIDE0LjEyMUwxOSAxOW0tNy03bDctN20tNyA3bC0yLjg3OSAyLjg3OU0xMiAxMkw5LjEyMSA5LjEyMW0wIDUuNzU4YTMgMyAwIDEwLTQuMjQzIDQuMjQzIDMgMyAwIDAwNC4yNDMtNC4yNDN6bTAtNS43NThhMyAzIDAgMTAtNC4yNDMtNC4yNDMgMyAzIDAgMDA0LjI0MyA0LjI0M3oiIC8+Cjwvc3ZnPg==')
no-repeat center / 6mm 4mm;
content: '';
top: -2mm;
left: 10mm;
}
.qr-bill > .slip::after {
top: 10mm;
left: 59mm;
transform: rotate(90deg);
}
The html then is quite self explaining. You have the receipt and payment part, both have different required sections that need to be filled in.
The QR-Code is generated on the fly using the QR-Code element. Each line holds the information for a given field, best is to consult the documentation to see the exact syntax as there are a few options available. However, the data can be simple written in the qr-code element.
<div class="qr-bill">
<div class="slip">
<div class="receipt">
<section class="title">
Receipt
</section>
<section class="information">
<dl>
<dt>Account / Payable to</dt>
<dd>CH44 0000 0000 0000 0</dd>
<dt>Reference</dt>
<dd>21 0000 00003 14756 74457 51474
Order from 15.10.2020</dd>
<dt>Additional information</dt>
<dd>Invoice 2344235 - Gardening work</dd>
<dt>Payable by</dt>
<dd>Max Muster & Söhne
Musterstrasse 123
8000 Seldwyla</dd>
</dl>
</section>
<section class="amount">
<table class="amount">
<tr>
<th class="currency">Currency</th>
<th class="amount">Amount</th>
</tr>
<tr>
<td class="currency">CHF</td>
<td class="amount">1 521.52</td>
</tr>
</table>
</section>
<section class="acceptance-point">
<div class="label">Acceptance point</div>
</section>
</div>
<div class="payment">
<section class="title">
Payment part
</section>
<section class="qrcode">
<qr-code>SPC
0200
1
CH440000000000000
S
Max Muster & Söhne
Musterstrasse
123
8000
Seldwyla
CH
1521.52
CHF
S
Simon Muster
Musterstrasse
1
8000
Seldwyla
CH
QRR
21000000003147567445751474
Order from 15.10. 2020
EPD</qr-code>
</section>
<section class="amount">
<table class="amount">
<tr>
<th class="currency">Currency</th>
<th class="amount">Amount</th>
</tr>
<tr>
<td class="currency">CHF</td>
<td class="amount">1 521.52</td>
</tr>
</table>
</section>
<section class="information">
<dl>
<dt>Account / Payable to</dt>
<dd>CH44 0000 0000 0000 0</dd>
<dt>Reference</dt>
<dd>21 0000 00003 14756 74457 51474
Order from 15.10.2020</dd>
<dt>Additional information</dt>
<dd>Invoice 2344235 - Gardening work</dd>
<dt>Payable by</dt>
<dd>Max Muster & Söhne
Musterstrasse 123
8000 Seldwyla</dd>
</dl>
</section>
<section class="further-information"></section>
</div>
</div>
</div>
There is also a validator for your data at validation.iso-payments.ch, it requires to create an account though with sixed.
Bellow is a full example.