QR codes & barcodes
Two elements draw machine-readable codes: qr for QR codes and barcode for 1D barcodes. Both are generated inside the renderer, with no network call and no image to supply. Neither moves the cursor, so follow each with a move or place it in an at or a table cell.
#Scan-to-pay QR code (EPC)
An EPC QR code, also called a GiroCode, encodes a SEPA credit transfer. The customer scans it in their banking app, and the payee, IBAN, amount and reference are filled in. They only confirm. It is a push payment the payer approves in their own bank: nothing is charged automatically and there is no callback. You reconcile incoming transfers by the reference, which is usually your invoice number.
{ "type": "qr", "kind": "epc", "width": 110,
"name": "${payment.name}", "iban": "${payment.iban}", "bic": "${payment.bic}",
"amount": "${payment.amount}", "currency": "EUR",
"remittance": "${invoice.number}" }
'payment' => { 'name' => 'Meridian Instruments SAS', 'iban' => 'FR7630006000011234567890189',
'bic' => 'CRLYFRPP', 'amount' => '2430.21' }
| Field | Notes |
|---|---|
name | The beneficiary, that is you. Required, 70 characters at most. |
iban | The beneficiary's IBAN. Required, 34 characters at most. |
bic | Optional within the EEA. |
amount | A decimal number such as 2430.21, without a currency symbol or thousands separator. Between 0.01 and 999,999,999.99. Leave it empty to let the payer type the amount. |
currency | Must be EUR, which is also the default. |
remittance | The unstructured reference, 140 characters at most. |
purpose | An optional four-letter purpose code. |
width | The side of the square, in points. |
All fields are ${…}-interpolated. The whole payload must stay under 331 bytes, and the code uses error-correction level M, as the EPC standard requires.
Note EPC codes are EUR and SEPA only. Banking apps across the SEPA area read them, and they are ubiquitous in Germany, Austria and the Netherlands. Invalid input, such as another currency or a missing IBAN, skips the code with a warning instead of failing the render.
With a typed Factur-X invoice, give the seller an iban (and optionally a bic) and a ready-made payment.* block appears in the render data: payment.name, payment.iban, payment.bic, payment.amount, payment.currency and payment.remittance.
See it in the VAT-compliant invoice and the receipt.
#Free-text QR code
kind: "text" encodes value as is: a URL, a tracking number, a vCard.
{ "type": "qr", "kind": "text", "value": "https://track.example/${order.id}", "width": 80 }
#Barcodes
{ "type": "barcode", "symbology": "code128", "value": "ORDER-${order.id}",
"width": 220, "height": 56, "humanReadable": true }
| Field | Meaning |
|---|---|
symbology | code128, ean13, ean8 or code39. |
value | The data, ${…}-interpolated. |
width, height | The size of the bars, in points. |
humanReadable | Print the value as a caption below the bars. |
| Symbology | Accepts |
|---|---|
code128 | Any printable ASCII. The general-purpose choice for order and parcel numbers. |
ean13 | 12 digits. The check digit is computed for you. |
ean8 | 7 digits. The check digit is computed. |
code39 | Upper-case letters, digits, and - . $ / + % and space. |
Data that doesn't fit the symbology, such as 11 digits for an EAN-13 or a lower-case letter in Code 39, skips the barcode and adds a warning to Soli::PDF.last_warnings.
#Placing a code
Codes don't move the cursor, which lets text sit beside them. The payment panel of the compliant invoice draws the QR code, shifts the cursor right with move, writes the payment details next to it, then shifts back:
{ "type": "box", "fill": "F7F9FC", "border": "D7E0EC", "borderWidth": 0.8, "padding": 14, "content": [
{ "type": "qr", "kind": "epc", "name": "${seller.name}", "iban": "${payment.iban}",
"amount": "${payment.amount}", "remittance": "${invoice.number}", "width": 64 },
{ "type": "move", "x": 80, "y": 0 },
{ "type": "paragraph", "value": "IBAN ${payment.iban}", "options": { "fontSize": 9 } },
{ "type": "paragraph", "value": "Reference ${invoice.number}", "options": { "fontSize": 9 } },
{ "type": "move", "x": -80, "y": 30 }
] }
The last move returns to the left edge and goes down far enough to clear the code, so the box measures its full height. To put text below a code instead, move down by the code's height:
[
{ "type": "qr", "kind": "text", "value": "https://example.com", "width": 72 },
{ "type": "move", "y": 80 },
{ "type": "paragraph", "value": "Scan to open the online version.", "options": { "fontSize": 8 } }
]