Decode and encode MergeID electronic invoice QR codes
Project description
qrid — Python
Decode and encode MergeID electronic invoice QR codes.
Python port of qrid/codec (PHP). All three implementations (PHP, Node.js, Python) share the same payload format and function signatures, so QR codes generated by any one of them scan correctly in the others.
Typical usage flow
sequenceDiagram
actor Staff as Billing Staff
participant ERP as Billing / ERP System
participant Lib as qrid
participant QR as Invoice QR Code
participant App as MergeID App
Staff->>ERP: create invoice
ERP->>Lib: encode_qr_id(id, company, email, address, activity_code)
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: decode_qr_id(encoded)
Lib-->>App: { v, id, company, email, address, activity_code }
App-->>Staff: display verified invoice identity
Installation
# Decode only (no extra dependencies)
pip install qrid
# Decode + encode SVG
pip install 'qrid[encode]'
Usage
Decode
from qrid import decode_qr_id
# `encoded` is the raw string value scanned from a MergeID QR code
payload = decode_qr_id(encoded)
print(payload["v"]) # Payload schema version (int, currently 1)
print(payload["id"]) # Tax or company ID (e.g. "3101679980")
print(payload["company"]) # Company legal name
print(payload["email"]) # Billing e-mail address
print(payload["address"]) # Physical address
print(payload["activity_code"]) # Installation / activity code (e.g. "ACT-001"), or "" if blank
decode_qr_id strips surrounding whitespace before decoding, so strings
copied with accidental padding are handled transparently.
Exceptions raised:
| Exception | Cause |
|---|---|
ValueError |
Input is not valid base64 |
json.JSONDecodeError |
Decoded bytes are not valid JSON |
import json
from qrid import decode_qr_id
try:
payload = decode_qr_id(raw)
except ValueError:
# QR data was not base64
...
except json.JSONDecodeError:
# QR data decoded but was not the expected JSON structure
...
Encode (requires qrid[encode])
from qrid import encode_qr_id
svg = encode_qr_id(
id="3101679980",
company="Acme Corp S.A.",
email="billing@acme.example",
address="123 Main St, San José, Costa Rica",
activity_code="ACT-001",
)
# Write to a file
with open("invoice_qr.svg", "w") as f:
f.write(svg)
# Or serve directly
# Content-Type: image/svg+xml
activity_code 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 raised:
| Exception | Cause |
|---|---|
ImportError |
segno is not installed (pip install 'qrid[encode]') |
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 |
str |
Tax / company registration ID. |
company |
str |
Legal company name (UTF-8, including accented characters). |
email |
str |
Primary billing or contact e-mail address. |
address |
str |
Physical address of the company. |
activity_code |
str |
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 |
|---|---|---|
| Python | >= 3.9 |
Always |
segno |
>= 1.6 |
encode_qr_id() only |
Running tests
pip install 'qrid[dev]'
pytest
Publishing
Releases to PyPI are fully automated — there is no manual twine upload step:
- Bump
versioninpyproject.tomland merge tomain. release.ymlruns on every push tomain. It reads the version frompyproject.toml; if novX.Y.Ztag already exists for it, it runs the test suite and creates that tag plus a GitHub Release.- In that same run,
release.ymldispatchespublish.ymlvia the GitHub API (gh workflow run publish.yml), which re-runs the tests, builds the sdist/wheel, and uploads to PyPI using Trusted Publishing (OIDC) — no stored API token.
publish.yml is dispatched as an independent, top-level run via the Actions API rather than invoked as a reusable workflow (workflow_call) or relying on the release: published event. Both alternatives were tried and both are broken for this use case:
release: publishednever fires for releases created with the Actions-internalGITHUB_TOKEN(GitHub's anti-recursion guard).workflow_callchaining runspublish.ymlas a job withinrelease.yml's own run, so the OIDC certificate's build-config claim identifies the caller (release.yml) instead ofpublish.yml— PyPI's Trusted Publisher verification then rejects the upload outright (does not match expected Trusted Publisher). PyPI's own docs confirm reusable workflows are not supported for Trusted Publishing.
A workflow_dispatch API call made with GITHUB_TOKEN is explicitly exempt from the anti-recursion guard and starts a genuine new top-level run of publish.yml, so its OIDC certificate correctly identifies publish.yml as the source. publish.yml also still accepts release: published (for a release cut by hand) and workflow_dispatch (manual re-run) as a fallback.
Pushes to main that don't change the version are a no-op for release.yml (the tag already exists), so unrelated commits (docs, CI tweaks) don't trigger a release.
One-time PyPI setup (already done for this project, documented here for reference): add a pending trusted publisher at pypi.org/manage/account/publishing with owner Quality-XP-Development-SESSA, repository qrid-python, workflow filename publish.yml, and environment name pypi; the same pypi environment must exist under the repo's GitHub Settings → Environments.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qrid-1.1.0.tar.gz.
File metadata
- Download URL: qrid-1.1.0.tar.gz
- Upload date:
- Size: 6.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1472b2580db0098c81a2377b3f01e03c466df621f7b1786f865462b9b52823e6
|
|
| MD5 |
babf4c6e16b1af125ffb62a58af6b133
|
|
| BLAKE2b-256 |
f1d4e5003da527d4866e4847605cc4f440a97612c3dcf1106e8cae64ee363d5c
|
Provenance
The following attestation bundles were made for qrid-1.1.0.tar.gz:
Publisher:
publish.yml on Quality-XP-Development-SESSA/qrid-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qrid-1.1.0.tar.gz -
Subject digest:
1472b2580db0098c81a2377b3f01e03c466df621f7b1786f865462b9b52823e6 - Sigstore transparency entry: 2136884596
- Sigstore integration time:
-
Permalink:
Quality-XP-Development-SESSA/qrid-python@6d061ab515a699ef3b330738c7c382a320ef841c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Quality-XP-Development-SESSA
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6d061ab515a699ef3b330738c7c382a320ef841c -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file qrid-1.1.0-py3-none-any.whl.
File metadata
- Download URL: qrid-1.1.0-py3-none-any.whl
- Upload date:
- Size: 5.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ed79c01130037025226cd23e5aa569320a682404b805e2e1741c8f50526c4ba
|
|
| MD5 |
8735222f7f1d20b32c050d45aaa9bd3a
|
|
| BLAKE2b-256 |
de94ab7e2d24c24bd7f5eff59dddbd26c5f479ae1060efb1bd61790337519ea8
|
Provenance
The following attestation bundles were made for qrid-1.1.0-py3-none-any.whl:
Publisher:
publish.yml on Quality-XP-Development-SESSA/qrid-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qrid-1.1.0-py3-none-any.whl -
Subject digest:
0ed79c01130037025226cd23e5aa569320a682404b805e2e1741c8f50526c4ba - Sigstore transparency entry: 2136884611
- Sigstore integration time:
-
Permalink:
Quality-XP-Development-SESSA/qrid-python@6d061ab515a699ef3b330738c7c382a320ef841c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Quality-XP-Development-SESSA
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6d061ab515a699ef3b330738c7c382a320ef841c -
Trigger Event:
workflow_dispatch
-
Statement type: