qrid / codec
Decode and encode MergeID electronic invoice QR codes
Requires
- php: >=8.1
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
- chillerlan/php-qrcode: ^5.0 — required for encodeQRId() SVG generation
README
PHP library for encoding and decoding MergeID electronic invoice QR codes.
MergeID QR codes embed invoice identity information (company, tax ID, contact, address) as a base64-encoded JSON payload. This library handles the encode/decode round-trip and, optionally, SVG QR image generation.
Typical usage flow
sequenceDiagram
actor Staff as Billing Staff
participant ERP as Billing / ERP System
participant Lib as qrid/codec
participant QR as Invoice QR Code
participant App as MergeID App
Staff->>ERP: create invoice
ERP->>Lib: Codec::encodeQRId(id, company, email, address, activityCode)
Lib-->>ERP: SVG QR code
ERP->>QR: print / embed on invoice
Note over App,QR: later, at point of scan
App->>QR: scan with camera
QR-->>App: base64 payload string
App->>Lib: Codec::decodeQRId(encoded)
Lib-->>App: { v, id, company, email, address, activity_code }
App-->>Staff: display verified invoice identity
Loading
Installation
# Decode only (no extra dependencies) composer require qrid/codec # Decode + encode SVG (adds QR generation library) composer require qrid/codec chillerlan/php-qrcode
Usage
Decode
Pass the raw string value scanned from a QR code:
use QRId\Codec; $payload = Codec::decodeQRId($encoded); echo $payload['v']; // Payload schema version (int, currently 1) echo $payload['id']; // Tax or company ID (e.g. "3101679980") echo $payload['company']; // Company legal name echo $payload['email']; // Billing e-mail address echo $payload['address']; // Physical address echo $payload['activity_code']; // Installation / activity code (e.g. "ACT-001"), or "" if blank
decodeQRId trims surrounding whitespace from the input before decoding, so strings
copied with accidental padding are handled transparently.
Exceptions thrown:
| Exception | Cause |
|---|---|
InvalidArgumentException |
Input is not valid base64 |
JsonException |
Decoded bytes are not valid JSON |
use InvalidArgumentException; use JsonException; use QRId\Codec; try { $payload = Codec::decodeQRId($raw); } catch (InvalidArgumentException $e) { // QR data was not base64 } catch (JsonException $e) { // QR data decoded but was not the expected JSON structure }
Encode (requires chillerlan/php-qrcode)
Produce an SVG QR code from invoice identity fields:
use QRId\Codec; $svg = Codec::encodeQRId( id: '3101679980', company: 'Acme Corp S.A.', email: 'billing@acme.example', address: '123 Main St, San José, Costa Rica', activityCode: 'ACT-001', ); // Serve inline header('Content-Type: image/svg+xml'); echo $svg; // Or embed in HTML echo '<img src="data:image/svg+xml;utf8,' . rawurlencode($svg) . '">';
activityCode is optional and defaults to '' (blank). A blank activity code signals a
consuming system to generate an electronic ticket instead of using an activity code.
Exceptions thrown:
| Exception | Cause |
|---|---|
RuntimeException |
chillerlan/php-qrcode is not installed |
JsonException |
JSON encoding of the payload failed (should not occur in practice) |
Payload format
The QR code data is a UTF-8 JSON object encoded as standard base64 (no line-breaks):
{
"v": 1,
"id": "3101679980",
"company": "Acme Corp S.A.",
"email": "billing@acme.example",
"address": "123 Main St, San José, Costa Rica",
"activity_code": "ACT-001"
}
| Field | Type | Description |
|---|---|---|
v |
int |
Payload schema version. Currently always 1. |
id |
string |
Tax / company registration ID. |
company |
string |
Legal company name (UTF-8, including accented characters). |
email |
string |
Primary billing or contact e-mail address. |
address |
string |
Physical address of the company. |
activity_code |
string |
Installation or activity code that links the QR to an internal record. May be blank (""), which signals a consuming system to generate an electronic ticket instead. |
Requirements
| Dependency | Version | Required for |
|---|---|---|
| PHP | >= 8.1 |
Always |
chillerlan/php-qrcode |
^5.0 |
encodeQRId() only |
Running tests
composer install
composer test
Tests cover field decoding, UTF-8 handling, whitespace trimming, error paths, and (when chillerlan/php-qrcode is available) the full encode-decode round-trip.
Publishing
New versions reach Packagist automatically — there is no build/upload step:
- Bump the version in the
VERSIONfile (repo root) and merge tomain. release.ymlruns on every push tomain. It readsVERSION; if novX.Y.Ztag already exists for it, it runs PHPUnit across PHP 8.2–8.4 (PHPUnit 11's minimum, even though the library itself supports PHP >= 8.1) and creates that tag plus a GitHub Release.- Packagist's webhook on this repository picks up the new tag and republishes it within moments. Composer installs straight from the tagged GitHub source (via
dist/zipball), so — unlike PyPI or npm — there's no artifact to build/upload and no OIDC/trusted-publisher concept to configure.
composer.json intentionally has no version field: Composer/Packagist derive the version purely from git tags, and a hardcoded field risks drifting out of sync with the actual tag. VERSION exists only so the release workflow has something to read.
Pushes to main that don't change VERSION are a no-op (the tag already exists), so unrelated commits (docs, CI tweaks) don't trigger a release.
License
MIT