soli-pdf

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' }
FieldNotes
nameThe beneficiary, that is you. Required, 70 characters at most.
ibanThe beneficiary's IBAN. Required, 34 characters at most.
bicOptional within the EEA.
amountA 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.
currencyMust be EUR, which is also the default.
remittanceThe unstructured reference, 140 characters at most.
purposeAn optional four-letter purpose code.
widthThe 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 }
FieldMeaning
symbologycode128, ean13, ean8 or code39.
valueThe data, ${…}-interpolated.
width, heightThe size of the bars, in points.
humanReadablePrint the value as a caption below the bars.
SymbologyAccepts
code128Any printable ASCII. The general-purpose choice for order and parcel numbers.
ean1312 digits. The check digit is computed for you.
ean87 digits. The check digit is computed.
code39Upper-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 } }
]