qrid 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qrid/__init__.py +3 -0
- qrid/codec.py +90 -0
- qrid-1.0.0.dist-info/METADATA +151 -0
- qrid-1.0.0.dist-info/RECORD +5 -0
- qrid-1.0.0.dist-info/WHEEL +4 -0
qrid/__init__.py
ADDED
qrid/codec.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
from typing import TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class QRIdPayload(TypedDict):
|
|
9
|
+
v: int
|
|
10
|
+
code: str
|
|
11
|
+
id: str
|
|
12
|
+
company: str
|
|
13
|
+
email: str
|
|
14
|
+
address: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def decode_qr_id(encoded: str) -> QRIdPayload:
|
|
18
|
+
"""Decode a base64-encoded QR ID string into its structured payload.
|
|
19
|
+
|
|
20
|
+
Mirrors QRId\\Codec::decodeQRId() in qrid/codec (PHP).
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
encoded: The base64 string read from a MergeID QR code.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A TypedDict with keys: v, code, id, company, email, address.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: When the base64 input is malformed.
|
|
30
|
+
json.JSONDecodeError: When the decoded content is not valid JSON.
|
|
31
|
+
"""
|
|
32
|
+
trimmed = encoded.strip()
|
|
33
|
+
try:
|
|
34
|
+
json_bytes = base64.b64decode(trimmed, validate=True)
|
|
35
|
+
except binascii.Error:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"Invalid base64 input: could not decode the provided string."
|
|
38
|
+
)
|
|
39
|
+
return json.loads(json_bytes.decode("utf-8"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def encode_qr_id(
|
|
43
|
+
code: str,
|
|
44
|
+
id: str,
|
|
45
|
+
company: str,
|
|
46
|
+
email: str,
|
|
47
|
+
address: str,
|
|
48
|
+
) -> str:
|
|
49
|
+
"""Encode invoice identity fields and return an SVG QR code string.
|
|
50
|
+
|
|
51
|
+
Mirrors QRId\\Codec::encodeQRId() in qrid/codec (PHP).
|
|
52
|
+
Requires the encode extra: pip install 'qrid[encode]'
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
code: Installation or activity code.
|
|
56
|
+
id: Tax / company registration ID.
|
|
57
|
+
company: Company legal name (UTF-8).
|
|
58
|
+
email: Primary billing or contact e-mail address.
|
|
59
|
+
address: Physical address of the company.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
An SVG markup string containing the generated QR code.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ImportError: When the segno package is not installed.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
import segno
|
|
69
|
+
except ImportError:
|
|
70
|
+
raise ImportError(
|
|
71
|
+
"segno is required for encode_qr_id(). "
|
|
72
|
+
"Install with: pip install 'qrid[encode]'"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
payload: QRIdPayload = {
|
|
76
|
+
"v": 1,
|
|
77
|
+
"code": code,
|
|
78
|
+
"id": id,
|
|
79
|
+
"company": company,
|
|
80
|
+
"email": email,
|
|
81
|
+
"address": address,
|
|
82
|
+
}
|
|
83
|
+
encoded = base64.b64encode(
|
|
84
|
+
json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
85
|
+
).decode()
|
|
86
|
+
|
|
87
|
+
qr = segno.make_qr(encoded)
|
|
88
|
+
buf = io.BytesIO()
|
|
89
|
+
qr.save(buf, kind="svg", nl=False)
|
|
90
|
+
return buf.getvalue().decode("utf-8")
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qrid
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Decode and encode MergeID electronic invoice QR codes
|
|
5
|
+
Project-URL: Homepage, https://github.com/Quality-XP-Development-SESSA/qrid-python
|
|
6
|
+
Project-URL: Repository, https://github.com/Quality-XP-Development-SESSA/qrid-python
|
|
7
|
+
Author-email: Valentin Secades Mendez <vsecades@qxdev.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: codec,decoder,invoice,mergeid,qr,qrcode
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: segno>=1.6; extra == 'dev'
|
|
22
|
+
Provides-Extra: encode
|
|
23
|
+
Requires-Dist: segno>=1.6; extra == 'encode'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# qrid — Python
|
|
27
|
+
|
|
28
|
+
Decode and encode **MergeID electronic invoice QR codes**.
|
|
29
|
+
|
|
30
|
+
Python port of [`qrid/codec`](https://packagist.org/packages/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.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Decode only (no extra dependencies)
|
|
36
|
+
pip install qrid
|
|
37
|
+
|
|
38
|
+
# Decode + encode SVG
|
|
39
|
+
pip install 'qrid[encode]'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Decode
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from qrid import decode_qr_id
|
|
48
|
+
|
|
49
|
+
# `encoded` is the raw string value scanned from a MergeID QR code
|
|
50
|
+
payload = decode_qr_id(encoded)
|
|
51
|
+
|
|
52
|
+
print(payload["v"]) # Payload schema version (int, currently 1)
|
|
53
|
+
print(payload["code"]) # Installation / activity code (e.g. "ACT-001")
|
|
54
|
+
print(payload["id"]) # Tax or company ID (e.g. "3101679980")
|
|
55
|
+
print(payload["company"]) # Company legal name
|
|
56
|
+
print(payload["email"]) # Billing e-mail address
|
|
57
|
+
print(payload["address"]) # Physical address
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`decode_qr_id` strips surrounding whitespace before decoding, so strings
|
|
61
|
+
copied with accidental padding are handled transparently.
|
|
62
|
+
|
|
63
|
+
**Exceptions raised:**
|
|
64
|
+
|
|
65
|
+
| Exception | Cause |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `ValueError` | Input is not valid base64 |
|
|
68
|
+
| `json.JSONDecodeError` | Decoded bytes are not valid JSON |
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import json
|
|
72
|
+
from qrid import decode_qr_id
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
payload = decode_qr_id(raw)
|
|
76
|
+
except ValueError:
|
|
77
|
+
# QR data was not base64
|
|
78
|
+
...
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
# QR data decoded but was not the expected JSON structure
|
|
81
|
+
...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Encode (requires `qrid[encode]`)
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from qrid import encode_qr_id
|
|
88
|
+
|
|
89
|
+
svg = encode_qr_id(
|
|
90
|
+
code="ACT-001",
|
|
91
|
+
id="3101679980",
|
|
92
|
+
company="Acme Corp S.A.",
|
|
93
|
+
email="billing@acme.example",
|
|
94
|
+
address="123 Main St, San José, Costa Rica",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Write to a file
|
|
98
|
+
with open("invoice_qr.svg", "w") as f:
|
|
99
|
+
f.write(svg)
|
|
100
|
+
|
|
101
|
+
# Or serve directly
|
|
102
|
+
# Content-Type: image/svg+xml
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Exceptions raised:**
|
|
106
|
+
|
|
107
|
+
| Exception | Cause |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `ImportError` | `segno` is not installed (`pip install 'qrid[encode]'`) |
|
|
110
|
+
|
|
111
|
+
## Payload format
|
|
112
|
+
|
|
113
|
+
The QR code data is a UTF-8 JSON object encoded as standard base64 (no line-breaks):
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"v": 1,
|
|
118
|
+
"code": "ACT-001",
|
|
119
|
+
"id": "3101679980",
|
|
120
|
+
"company": "Acme Corp S.A.",
|
|
121
|
+
"email": "billing@acme.example",
|
|
122
|
+
"address": "123 Main St, San José, Costa Rica"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Field | Type | Description |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| `v` | `int` | Payload schema version. Currently always `1`. |
|
|
129
|
+
| `code` | `str` | Installation or activity code that links the QR to an internal record. |
|
|
130
|
+
| `id` | `str` | Tax / company registration ID. |
|
|
131
|
+
| `company` | `str` | Legal company name (UTF-8, including accented characters). |
|
|
132
|
+
| `email` | `str` | Primary billing or contact e-mail address. |
|
|
133
|
+
| `address` | `str` | Physical address of the company. |
|
|
134
|
+
|
|
135
|
+
## Requirements
|
|
136
|
+
|
|
137
|
+
| Dependency | Version | Required for |
|
|
138
|
+
| --- | --- | --- |
|
|
139
|
+
| Python | `>= 3.9` | Always |
|
|
140
|
+
| `segno` | `>= 1.6` | `encode_qr_id()` only |
|
|
141
|
+
|
|
142
|
+
## Running tests
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pip install 'qrid[dev]'
|
|
146
|
+
pytest
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
qrid/__init__.py,sha256=9RQQFhDlMpfervwISGgFJrHMtHsLQFa0Hrm6YSRSOTw,118
|
|
2
|
+
qrid/codec.py,sha256=_MMhzgR6kMYlmL7CRIot8UoGmnzzWcRdaodaCfXQdB8,2319
|
|
3
|
+
qrid-1.0.0.dist-info/METADATA,sha256=UZjaJHMcvwGBM2ZN7tMpa0f9PuV2BnBXQgzRGAfzhvw,4082
|
|
4
|
+
qrid-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
5
|
+
qrid-1.0.0.dist-info/RECORD,,
|