cheqi-sdk 0.1.0__tar.gz
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.
- cheqi_sdk-0.1.0/.github/workflows/publish.yml +49 -0
- cheqi_sdk-0.1.0/.gitignore +17 -0
- cheqi_sdk-0.1.0/PKG-INFO +149 -0
- cheqi_sdk-0.1.0/README.md +123 -0
- cheqi_sdk-0.1.0/pyproject.toml +56 -0
- cheqi_sdk-0.1.0/src/cheqi/__init__.py +26 -0
- cheqi_sdk-0.1.0/src/cheqi/config.py +104 -0
- cheqi_sdk-0.1.0/src/cheqi/exceptions.py +82 -0
- cheqi_sdk-0.1.0/src/cheqi/http/__init__.py +0 -0
- cheqi_sdk-0.1.0/src/cheqi/http/client.py +456 -0
- cheqi_sdk-0.1.0/src/cheqi/http/endpoints.py +31 -0
- cheqi_sdk-0.1.0/src/cheqi/http/retry.py +115 -0
- cheqi_sdk-0.1.0/src/cheqi/models/__init__.py +156 -0
- cheqi_sdk-0.1.0/src/cheqi/models/base.py +52 -0
- cheqi_sdk-0.1.0/src/cheqi/models/company.py +70 -0
- cheqi_sdk-0.1.0/src/cheqi/models/credit_note.py +221 -0
- cheqi_sdk-0.1.0/src/cheqi/models/encryption.py +47 -0
- cheqi_sdk-0.1.0/src/cheqi/models/enums.py +240 -0
- cheqi_sdk-0.1.0/src/cheqi/models/envelopes.py +85 -0
- cheqi_sdk-0.1.0/src/cheqi/models/matching.py +47 -0
- cheqi_sdk-0.1.0/src/cheqi/models/payment.py +42 -0
- cheqi_sdk-0.1.0/src/cheqi/models/receipt.py +293 -0
- cheqi_sdk-0.1.0/src/cheqi/py.typed +0 -0
- cheqi_sdk-0.1.0/src/cheqi/sdk.py +134 -0
- cheqi_sdk-0.1.0/src/cheqi/services/__init__.py +0 -0
- cheqi_sdk-0.1.0/src/cheqi/services/company.py +17 -0
- cheqi_sdk-0.1.0/src/cheqi/services/credit_note.py +189 -0
- cheqi_sdk-0.1.0/src/cheqi/services/decryption.py +174 -0
- cheqi_sdk-0.1.0/src/cheqi/services/encryption.py +136 -0
- cheqi_sdk-0.1.0/src/cheqi/services/matching.py +101 -0
- cheqi_sdk-0.1.0/src/cheqi/services/receipt.py +292 -0
- cheqi_sdk-0.1.0/src/cheqi/services/store.py +51 -0
- cheqi_sdk-0.1.0/src/cheqi/services/verification.py +35 -0
- cheqi_sdk-0.1.0/src/cheqi/utils/__init__.py +0 -0
- cheqi_sdk-0.1.0/src/cheqi/utils/hash.py +15 -0
- cheqi_sdk-0.1.0/src/cheqi/utils/pem.py +59 -0
- cheqi_sdk-0.1.0/src/cheqi/utils/rfc8785.py +119 -0
- cheqi_sdk-0.1.0/tests/__init__.py +0 -0
- cheqi_sdk-0.1.0/tests/conftest.py +38 -0
- cheqi_sdk-0.1.0/tests/test_encryption.py +143 -0
- cheqi_sdk-0.1.0/tests/test_models.py +165 -0
- cheqi_sdk-0.1.0/tests/test_receipt_service.py +37 -0
- cheqi_sdk-0.1.0/tests/test_rfc8785.py +89 -0
- cheqi_sdk-0.1.0/tests/test_serialization.py +187 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check out repository
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install build dependencies
|
|
23
|
+
run: python -m pip install --upgrade build
|
|
24
|
+
|
|
25
|
+
- name: Build distribution artifacts
|
|
26
|
+
run: python -m build
|
|
27
|
+
|
|
28
|
+
- name: Upload distribution artifacts
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/*
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
needs: build
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
environment: pypi
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write
|
|
40
|
+
|
|
41
|
+
steps:
|
|
42
|
+
- name: Download distribution artifacts
|
|
43
|
+
uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist
|
|
47
|
+
|
|
48
|
+
- name: Publish package to PyPI
|
|
49
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
cheqi_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cheqi-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cheqi Python SDK for end-to-end encrypted receipt processing
|
|
5
|
+
Author-email: Cheqi <support@cheqi.io>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Requires-Dist: cryptography>=42.0
|
|
17
|
+
Requires-Dist: httpx>=0.27
|
|
18
|
+
Requires-Dist: pydantic>=2.5
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Cheqi Python SDK
|
|
28
|
+
|
|
29
|
+
Python SDK for end-to-end encrypted receipt and credit note processing via the Cheqi API.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install cheqi-sdk
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or with uv:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv add cheqi-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from cheqi import CheqiSDK, Environment
|
|
47
|
+
from cheqi.models import (
|
|
48
|
+
IdentificationDetails,
|
|
49
|
+
CardDetails,
|
|
50
|
+
ReceiptTemplateRequest,
|
|
51
|
+
Product,
|
|
52
|
+
Tax,
|
|
53
|
+
UnitCode,
|
|
54
|
+
PaymentType,
|
|
55
|
+
CardProvider,
|
|
56
|
+
)
|
|
57
|
+
from decimal import Decimal
|
|
58
|
+
|
|
59
|
+
# Initialize the SDK
|
|
60
|
+
sdk = CheqiSDK(
|
|
61
|
+
environment=Environment.PRODUCTION,
|
|
62
|
+
api_key="sk_live_...",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Build identification details
|
|
66
|
+
identification = IdentificationDetails(
|
|
67
|
+
payment_type=PaymentType.CARD_PAYMENT,
|
|
68
|
+
card_details=CardDetails(
|
|
69
|
+
payment_account_reference="PAR123456",
|
|
70
|
+
card_provider=CardProvider.VISA,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Build a receipt request
|
|
75
|
+
product = Product(
|
|
76
|
+
name="Laptop",
|
|
77
|
+
identifier="LAP-001",
|
|
78
|
+
quantity=1.0,
|
|
79
|
+
base_quantity=1.0,
|
|
80
|
+
unit_code=UnitCode.ONE,
|
|
81
|
+
unit_price=Decimal("1000.00"),
|
|
82
|
+
subtotal=Decimal("1000.00"),
|
|
83
|
+
total=Decimal("1210.00"),
|
|
84
|
+
taxes=[Tax(rate=21.0, type="VAT", taxable_amount=Decimal("1000.00"), amount=Decimal("210.00"))],
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
receipt_request = ReceiptTemplateRequest(
|
|
88
|
+
document_number="INV-2024-001",
|
|
89
|
+
currency="EUR",
|
|
90
|
+
receipt_subtotal=Decimal("1000.00"),
|
|
91
|
+
total_before_tax=Decimal("1000.00"),
|
|
92
|
+
total_tax_amount=Decimal("210.00"),
|
|
93
|
+
total_amount=Decimal("1210.00"),
|
|
94
|
+
products=[product],
|
|
95
|
+
taxes=[Tax(rate=21.0, type="VAT", taxable_amount=Decimal("1000.00"), amount=Decimal("210.00"))],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Process the complete receipt (match -> template -> encrypt -> send)
|
|
99
|
+
result = sdk.receipt_service.process_complete_receipt(
|
|
100
|
+
identification_details=identification,
|
|
101
|
+
receipt_request=receipt_request,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if result.success:
|
|
105
|
+
print(f"Receipt delivered! ID: {result.cheqi_receipt_id}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Chainable Model Building
|
|
109
|
+
|
|
110
|
+
Models are immutable. Convenience methods return new instances:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
request = (
|
|
114
|
+
ReceiptTemplateRequest(document_number="INV-001", currency="EUR")
|
|
115
|
+
.add_product(product1)
|
|
116
|
+
.add_product(product2)
|
|
117
|
+
.add_tax(tax1)
|
|
118
|
+
.add_discount(discount1)
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Authentication
|
|
123
|
+
|
|
124
|
+
Two modes are supported:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# API Key (for companies accessing their own data)
|
|
128
|
+
sdk = CheqiSDK(
|
|
129
|
+
environment=Environment.PRODUCTION,
|
|
130
|
+
api_key="sk_live_...",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# OAuth2 Client Credentials (for third-party integrations)
|
|
134
|
+
sdk = CheqiSDK(
|
|
135
|
+
environment=Environment.PRODUCTION,
|
|
136
|
+
client_id="your_client_id",
|
|
137
|
+
client_secret="your_client_secret",
|
|
138
|
+
)
|
|
139
|
+
# Pass access_token to service methods
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv sync --extra dev
|
|
146
|
+
uv run pytest
|
|
147
|
+
uv run ruff check src/ tests/
|
|
148
|
+
uv run mypy src/
|
|
149
|
+
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Cheqi Python SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for end-to-end encrypted receipt and credit note processing via the Cheqi API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install cheqi-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with uv:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add cheqi-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from cheqi import CheqiSDK, Environment
|
|
21
|
+
from cheqi.models import (
|
|
22
|
+
IdentificationDetails,
|
|
23
|
+
CardDetails,
|
|
24
|
+
ReceiptTemplateRequest,
|
|
25
|
+
Product,
|
|
26
|
+
Tax,
|
|
27
|
+
UnitCode,
|
|
28
|
+
PaymentType,
|
|
29
|
+
CardProvider,
|
|
30
|
+
)
|
|
31
|
+
from decimal import Decimal
|
|
32
|
+
|
|
33
|
+
# Initialize the SDK
|
|
34
|
+
sdk = CheqiSDK(
|
|
35
|
+
environment=Environment.PRODUCTION,
|
|
36
|
+
api_key="sk_live_...",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Build identification details
|
|
40
|
+
identification = IdentificationDetails(
|
|
41
|
+
payment_type=PaymentType.CARD_PAYMENT,
|
|
42
|
+
card_details=CardDetails(
|
|
43
|
+
payment_account_reference="PAR123456",
|
|
44
|
+
card_provider=CardProvider.VISA,
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Build a receipt request
|
|
49
|
+
product = Product(
|
|
50
|
+
name="Laptop",
|
|
51
|
+
identifier="LAP-001",
|
|
52
|
+
quantity=1.0,
|
|
53
|
+
base_quantity=1.0,
|
|
54
|
+
unit_code=UnitCode.ONE,
|
|
55
|
+
unit_price=Decimal("1000.00"),
|
|
56
|
+
subtotal=Decimal("1000.00"),
|
|
57
|
+
total=Decimal("1210.00"),
|
|
58
|
+
taxes=[Tax(rate=21.0, type="VAT", taxable_amount=Decimal("1000.00"), amount=Decimal("210.00"))],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
receipt_request = ReceiptTemplateRequest(
|
|
62
|
+
document_number="INV-2024-001",
|
|
63
|
+
currency="EUR",
|
|
64
|
+
receipt_subtotal=Decimal("1000.00"),
|
|
65
|
+
total_before_tax=Decimal("1000.00"),
|
|
66
|
+
total_tax_amount=Decimal("210.00"),
|
|
67
|
+
total_amount=Decimal("1210.00"),
|
|
68
|
+
products=[product],
|
|
69
|
+
taxes=[Tax(rate=21.0, type="VAT", taxable_amount=Decimal("1000.00"), amount=Decimal("210.00"))],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Process the complete receipt (match -> template -> encrypt -> send)
|
|
73
|
+
result = sdk.receipt_service.process_complete_receipt(
|
|
74
|
+
identification_details=identification,
|
|
75
|
+
receipt_request=receipt_request,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if result.success:
|
|
79
|
+
print(f"Receipt delivered! ID: {result.cheqi_receipt_id}")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Chainable Model Building
|
|
83
|
+
|
|
84
|
+
Models are immutable. Convenience methods return new instances:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
request = (
|
|
88
|
+
ReceiptTemplateRequest(document_number="INV-001", currency="EUR")
|
|
89
|
+
.add_product(product1)
|
|
90
|
+
.add_product(product2)
|
|
91
|
+
.add_tax(tax1)
|
|
92
|
+
.add_discount(discount1)
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Authentication
|
|
97
|
+
|
|
98
|
+
Two modes are supported:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# API Key (for companies accessing their own data)
|
|
102
|
+
sdk = CheqiSDK(
|
|
103
|
+
environment=Environment.PRODUCTION,
|
|
104
|
+
api_key="sk_live_...",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# OAuth2 Client Credentials (for third-party integrations)
|
|
108
|
+
sdk = CheqiSDK(
|
|
109
|
+
environment=Environment.PRODUCTION,
|
|
110
|
+
client_id="your_client_id",
|
|
111
|
+
client_secret="your_client_secret",
|
|
112
|
+
)
|
|
113
|
+
# Pass access_token to service methods
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv sync --extra dev
|
|
120
|
+
uv run pytest
|
|
121
|
+
uv run ruff check src/ tests/
|
|
122
|
+
uv run mypy src/
|
|
123
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cheqi-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Cheqi Python SDK for end-to-end encrypted receipt processing"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Cheqi", email = "support@cheqi.io" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"httpx>=0.27",
|
|
27
|
+
"pydantic>=2.5",
|
|
28
|
+
"cryptography>=42.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=7.0",
|
|
34
|
+
"pytest-httpx>=0.30",
|
|
35
|
+
"pytest-cov>=4.0",
|
|
36
|
+
"mypy>=1.0",
|
|
37
|
+
"ruff>=0.4",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/cheqi"]
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
target-version = "py39"
|
|
45
|
+
line-length = 120
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
select = ["E", "F", "I", "W"]
|
|
49
|
+
|
|
50
|
+
[tool.mypy]
|
|
51
|
+
python_version = "3.9"
|
|
52
|
+
strict = true
|
|
53
|
+
plugins = ["pydantic.mypy"]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Cheqi Python SDK for end-to-end encrypted receipt processing."""
|
|
2
|
+
|
|
3
|
+
from cheqi.config import CheqiSDKConfig, Environment
|
|
4
|
+
from cheqi.exceptions import (
|
|
5
|
+
CheqiApiError,
|
|
6
|
+
CheqiSDKError,
|
|
7
|
+
CreditNoteProcessingError,
|
|
8
|
+
DecryptionError,
|
|
9
|
+
EncryptionError,
|
|
10
|
+
ErrorCodes,
|
|
11
|
+
ReceiptProcessingError,
|
|
12
|
+
)
|
|
13
|
+
from cheqi.sdk import CheqiSDK
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CheqiSDK",
|
|
17
|
+
"CheqiSDKConfig",
|
|
18
|
+
"Environment",
|
|
19
|
+
"CheqiSDKError",
|
|
20
|
+
"CheqiApiError",
|
|
21
|
+
"EncryptionError",
|
|
22
|
+
"DecryptionError",
|
|
23
|
+
"ReceiptProcessingError",
|
|
24
|
+
"CreditNoteProcessingError",
|
|
25
|
+
"ErrorCodes",
|
|
26
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""SDK configuration and environment definitions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("cheqi.config")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Environment(str, Enum):
|
|
13
|
+
"""Predefined Cheqi API environments."""
|
|
14
|
+
|
|
15
|
+
SANDBOX = "https://sandbox.api.cheqi.io"
|
|
16
|
+
PRODUCTION = "https://api.cheqi.io"
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def base_url(self) -> str:
|
|
20
|
+
return self.value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CheqiSDKConfig:
|
|
24
|
+
"""Immutable SDK configuration.
|
|
25
|
+
|
|
26
|
+
Supports two authentication modes:
|
|
27
|
+
- API key: Bearer token (starts with sk_live_ or sk_test_)
|
|
28
|
+
- OAuth2: Client credentials (client_id + client_secret)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
*,
|
|
34
|
+
environment: Optional[Environment] = None,
|
|
35
|
+
custom_api_endpoint: Optional[str] = None,
|
|
36
|
+
api_key: Optional[str] = None,
|
|
37
|
+
client_id: Optional[str] = None,
|
|
38
|
+
client_secret: Optional[str] = None,
|
|
39
|
+
private_key: Optional[str] = None,
|
|
40
|
+
timeout_seconds: int = 30,
|
|
41
|
+
max_retries: int = 3,
|
|
42
|
+
) -> None:
|
|
43
|
+
# Resolve API endpoint
|
|
44
|
+
if custom_api_endpoint:
|
|
45
|
+
self._api_endpoint = custom_api_endpoint
|
|
46
|
+
elif environment:
|
|
47
|
+
self._api_endpoint = environment.base_url
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("Either 'environment' or 'custom_api_endpoint' must be provided")
|
|
50
|
+
|
|
51
|
+
self._api_key = api_key
|
|
52
|
+
self._client_id = client_id
|
|
53
|
+
self._client_secret = client_secret
|
|
54
|
+
self._private_key = private_key
|
|
55
|
+
|
|
56
|
+
if timeout_seconds <= 0:
|
|
57
|
+
raise ValueError(f"Timeout must be positive, got: {timeout_seconds}")
|
|
58
|
+
self._timeout_seconds = timeout_seconds
|
|
59
|
+
|
|
60
|
+
if max_retries < 0:
|
|
61
|
+
raise ValueError(f"Max retries cannot be negative, got: {max_retries}")
|
|
62
|
+
self._max_retries = max_retries
|
|
63
|
+
|
|
64
|
+
# Warn if no auth configured
|
|
65
|
+
has_api_key = bool(api_key and api_key.strip())
|
|
66
|
+
has_credentials = bool(client_id and client_id.strip() and client_secret and client_secret.strip())
|
|
67
|
+
|
|
68
|
+
if not has_api_key and not has_credentials:
|
|
69
|
+
logger.warning("No authentication configured. API calls will fail unless access tokens are provided.")
|
|
70
|
+
|
|
71
|
+
if has_api_key and not self._is_valid_api_key_format(api_key): # type: ignore[arg-type]
|
|
72
|
+
logger.warning("API key does not match expected format (sk_live_* or sk_test_*)")
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _is_valid_api_key_format(key: str) -> bool:
|
|
76
|
+
return key.startswith("sk_live_") or key.startswith("sk_test_")
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def api_endpoint(self) -> str:
|
|
80
|
+
return self._api_endpoint
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def api_key(self) -> Optional[str]:
|
|
84
|
+
return self._api_key
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def client_id(self) -> Optional[str]:
|
|
88
|
+
return self._client_id
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def client_secret(self) -> Optional[str]:
|
|
92
|
+
return self._client_secret
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def private_key(self) -> Optional[str]:
|
|
96
|
+
return self._private_key
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def timeout_seconds(self) -> int:
|
|
100
|
+
return self._timeout_seconds
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def max_retries(self) -> int:
|
|
104
|
+
return self._max_retries
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Exception hierarchy for the Cheqi SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorCodes:
|
|
9
|
+
"""Standard error codes used across the SDK."""
|
|
10
|
+
|
|
11
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
|
12
|
+
VALIDATION_ERROR = "VALIDATION_ERROR"
|
|
13
|
+
AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED"
|
|
14
|
+
AUTHORIZATION_FAILED = "AUTHORIZATION_FAILED"
|
|
15
|
+
CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND"
|
|
16
|
+
RECEIPT_NOT_FOUND = "RECEIPT_NOT_FOUND"
|
|
17
|
+
ENCRYPTION_ERROR = "ENCRYPTION_ERROR"
|
|
18
|
+
DECRYPTION_ERROR = "DECRYPTION_ERROR"
|
|
19
|
+
NETWORK_ERROR = "NETWORK_ERROR"
|
|
20
|
+
RATE_LIMITED = "RATE_LIMITED"
|
|
21
|
+
SERVER_ERROR = "SERVER_ERROR"
|
|
22
|
+
INVALID_RESPONSE = "INVALID_RESPONSE"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CheqiSDKError(Exception):
|
|
26
|
+
"""Base exception for all Cheqi SDK errors."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
message: str,
|
|
31
|
+
error_code: str = ErrorCodes.UNKNOWN_ERROR,
|
|
32
|
+
http_status_code: int = 0,
|
|
33
|
+
correlation_id: Optional[str] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.error_code = error_code
|
|
37
|
+
self.http_status_code = http_status_code
|
|
38
|
+
self.correlation_id = correlation_id
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CheqiApiError(CheqiSDKError):
|
|
42
|
+
"""Exception for HTTP/API communication errors."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
message: str,
|
|
47
|
+
error_code: str = ErrorCodes.UNKNOWN_ERROR,
|
|
48
|
+
http_status_code: int = 0,
|
|
49
|
+
correlation_id: Optional[str] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
super().__init__(message, error_code, http_status_code, correlation_id)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def is_retryable(self) -> bool:
|
|
55
|
+
"""Whether this error can be retried."""
|
|
56
|
+
return self.http_status_code in (408, 429) or 500 <= self.http_status_code < 600
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EncryptionError(CheqiSDKError):
|
|
60
|
+
"""Exception for encryption failures."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, message: str) -> None:
|
|
63
|
+
super().__init__(message, ErrorCodes.ENCRYPTION_ERROR)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DecryptionError(CheqiSDKError):
|
|
67
|
+
"""Exception for decryption failures."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, message: str) -> None:
|
|
70
|
+
super().__init__(message, ErrorCodes.DECRYPTION_ERROR)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ReceiptProcessingError(CheqiSDKError):
|
|
74
|
+
"""Exception for receipt processing failures."""
|
|
75
|
+
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CreditNoteProcessingError(CheqiSDKError):
|
|
80
|
+
"""Exception for credit note processing failures."""
|
|
81
|
+
|
|
82
|
+
pass
|
|
File without changes
|