tx-verify 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.
- tx_verify-0.1.0/.github/workflows/ci.yml +52 -0
- tx_verify-0.1.0/.github/workflows/publish.yml +50 -0
- tx_verify-0.1.0/.gitignore +44 -0
- tx_verify-0.1.0/.python-version +1 -0
- tx_verify-0.1.0/PKG-INFO +381 -0
- tx_verify-0.1.0/README.md +339 -0
- tx_verify-0.1.0/examples/abyssinia.py +36 -0
- tx_verify-0.1.0/examples/cbe.py +36 -0
- tx_verify-0.1.0/examples/cbe_birr.py +44 -0
- tx_verify-0.1.0/examples/dashen.py +43 -0
- tx_verify-0.1.0/examples/error_handling.py +45 -0
- tx_verify-0.1.0/examples/image.py +54 -0
- tx_verify-0.1.0/examples/mpesa.py +38 -0
- tx_verify-0.1.0/examples/telebirr.py +37 -0
- tx_verify-0.1.0/examples/universal.py +62 -0
- tx_verify-0.1.0/pyproject.toml +124 -0
- tx_verify-0.1.0/scripts/test_proxy_scenarios.py +312 -0
- tx_verify-0.1.0/tests/conftest.py +7 -0
- tx_verify-0.1.0/tests/test_verify_mpesa.py +80 -0
- tx_verify-0.1.0/tx_verify/__init__.py +39 -0
- tx_verify-0.1.0/tx_verify/services/__init__.py +0 -0
- tx_verify-0.1.0/tx_verify/services/verify_abyssinia.py +156 -0
- tx_verify-0.1.0/tx_verify/services/verify_cbe.py +177 -0
- tx_verify-0.1.0/tx_verify/services/verify_cbe_birr.py +318 -0
- tx_verify-0.1.0/tx_verify/services/verify_dashen.py +253 -0
- tx_verify-0.1.0/tx_verify/services/verify_image.py +156 -0
- tx_verify-0.1.0/tx_verify/services/verify_mpesa.py +275 -0
- tx_verify-0.1.0/tx_verify/services/verify_telebirr.py +305 -0
- tx_verify-0.1.0/tx_verify/services/verify_universal.py +138 -0
- tx_verify-0.1.0/tx_verify/utils/__init__.py +15 -0
- tx_verify-0.1.0/tx_verify/utils/error_handler.py +59 -0
- tx_verify-0.1.0/tx_verify/utils/http_client.py +355 -0
- tx_verify-0.1.0/tx_verify/utils/logger.py +74 -0
- tx_verify-0.1.0/uv.lock +1767 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint & Type Check
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- uses: astral-sh/ruff-action@v3
|
|
21
|
+
with:
|
|
22
|
+
args: "check"
|
|
23
|
+
|
|
24
|
+
- name: Check formatting
|
|
25
|
+
run: ruff format --check .
|
|
26
|
+
|
|
27
|
+
- name: Install mypy
|
|
28
|
+
run: pip install mypy
|
|
29
|
+
|
|
30
|
+
- name: Type check
|
|
31
|
+
run: mypy tx_verify/
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
needs: [lint]
|
|
37
|
+
strategy:
|
|
38
|
+
fail-fast: false
|
|
39
|
+
matrix:
|
|
40
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
|
|
44
|
+
- uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: ${{ matrix.python-version }}
|
|
47
|
+
|
|
48
|
+
- name: Install
|
|
49
|
+
run: pip install ".[dev]"
|
|
50
|
+
|
|
51
|
+
- name: Test
|
|
52
|
+
run: pytest -v
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*" # e.g. v3.0.1
|
|
7
|
+
- "[0-9]+.*" # bare version e.g. 3.0.1
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-publish:
|
|
11
|
+
name: Build & publish to PyPI
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # required for trusted publishing
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: pip install build
|
|
26
|
+
|
|
27
|
+
- name: Extract version
|
|
28
|
+
id: version
|
|
29
|
+
run: |
|
|
30
|
+
TAG="${{ github.ref_name }}"
|
|
31
|
+
# Strip leading 'v' if present
|
|
32
|
+
VERSION="${TAG#v}"
|
|
33
|
+
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
|
34
|
+
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
35
|
+
echo "Releasing version ${VERSION} from tag ${TAG}"
|
|
36
|
+
|
|
37
|
+
- name: Validate version matches tag
|
|
38
|
+
run: |
|
|
39
|
+
PKG_VERSION=$(python -c "import re; print(re.search(r'__version__ = \\\"([^\\\"]+)\\\"', open('tx_verify/__init__.py').read()).group(1))")
|
|
40
|
+
if [ "$PKG_VERSION" != "${{ steps.version.outputs.version }}" ]; then
|
|
41
|
+
echo "ERROR: pyproject.toml version ($PKG_VERSION) != tag version (${{ steps.version.outputs.version }})"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
echo "Version check passed: $PKG_VERSION"
|
|
45
|
+
|
|
46
|
+
- name: Build
|
|
47
|
+
run: python -m build
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[co]
|
|
4
|
+
*.pyc
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
wheels/
|
|
8
|
+
*.egg-info
|
|
9
|
+
*.egg
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.vscode/
|
|
18
|
+
.idea/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
*~
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Logs
|
|
28
|
+
logs/
|
|
29
|
+
|
|
30
|
+
# Environment
|
|
31
|
+
.env
|
|
32
|
+
.env.*
|
|
33
|
+
!.env.example
|
|
34
|
+
|
|
35
|
+
# Test
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
.coverage
|
|
38
|
+
htmlcov/
|
|
39
|
+
|
|
40
|
+
# MyPy
|
|
41
|
+
.mypy_cache/
|
|
42
|
+
|
|
43
|
+
# Ruff
|
|
44
|
+
.ruff_cache/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
tx_verify-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tx-verify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Transaction Verification API — Python library for verifying Ethiopian payment transactions (CBE, Telebirr, Dashen, Abyssinia, CBE Birr, M-Pesa).
|
|
5
|
+
Project-URL: Homepage, https://github.com/YOUR_USERNAME/tx-verify
|
|
6
|
+
Project-URL: Repository, https://github.com/YOUR_USERNAME/tx-verify
|
|
7
|
+
Project-URL: Issues, https://github.com/YOUR_USERNAME/tx-verify/issues
|
|
8
|
+
Author: Nahom d
|
|
9
|
+
License: ISC
|
|
10
|
+
Keywords: abyssinia,cbe,cbe-birr,dashen,ethiopia,m-pesa,payment,telebirr,transaction,verification
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
22
|
+
Requires-Dist: alembic>=1.13.0
|
|
23
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
24
|
+
Requires-Dist: httpx[socks]>=0.27.0
|
|
25
|
+
Requires-Dist: mistralai>=1.0.0
|
|
26
|
+
Requires-Dist: pillow>=10.0.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Requires-Dist: pypdf>=4.0.0
|
|
29
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
30
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# tx-verify
|
|
44
|
+
|
|
45
|
+
[](https://www.python.org/)
|
|
46
|
+
[](https://opensource.org/licenses/ISC)
|
|
47
|
+
|
|
48
|
+
> Python library for verifying Ethiopian payment transactions across multiple
|
|
49
|
+
> providers: **CBE**, **Telebirr**, **Dashen Bank**, **Bank of Abyssinia**,
|
|
50
|
+
> **CBE Birr**, and **M-Pesa**.
|
|
51
|
+
|
|
52
|
+
Each verifier fetches the official receipt from the provider (PDF or HTML),
|
|
53
|
+
parses it, and returns typed result objects. No headless browser is bundled —
|
|
54
|
+
PDFs are parsed with `pypdf` and HTML with `BeautifulSoup` — so it runs
|
|
55
|
+
anywhere Python does.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Supported Providers
|
|
60
|
+
|
|
61
|
+
| Provider | Function | Input (example) |
|
|
62
|
+
| ------------------ | -------------------- | --------------------------------------- |
|
|
63
|
+
| CBE | `verify_cbe()` | `reference="FT…"`, `account_suffix="…"` |
|
|
64
|
+
| Telebirr | `verify_telebirr()` | `reference="CE12345678"` |
|
|
65
|
+
| Dashen Bank | `verify_dashen()` | `transaction_reference="123…"` (16 dig) |
|
|
66
|
+
| Bank of Abyssinia | `verify_abyssinia()` | `reference="FT…"`, `suffix="…"` (5 dig) |
|
|
67
|
+
| CBE Birr | `verify_cbe_birr()` | `receipt="…"`, `phone="251…"` |
|
|
68
|
+
| M-Pesa | `verify_mpesa()` | `transaction_id="UE20VG1GS8"` |
|
|
69
|
+
| Image (Mistral AI) | `verify_image()` | `image_bytes`, auto-detects provider |
|
|
70
|
+
| Universal | `verify_universal()` | `reference` — auto-routes by format |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install tx-verify
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or with `uv`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv pip install tx-verify
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import asyncio
|
|
92
|
+
from tx_verify import verify_telebirr, verify_cbe
|
|
93
|
+
|
|
94
|
+
async def main():
|
|
95
|
+
# --- Telebirr ---
|
|
96
|
+
receipt = await verify_telebirr("CE12345678")
|
|
97
|
+
if receipt:
|
|
98
|
+
print(receipt.payer_name, receipt.settled_amount)
|
|
99
|
+
|
|
100
|
+
# --- CBE ---
|
|
101
|
+
result = await verify_cbe("FT23062669JJ", account_suffix="12345678")
|
|
102
|
+
if result.success:
|
|
103
|
+
print(f"Paid {result.amount} ETB to {result.receiver}")
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Examples
|
|
111
|
+
|
|
112
|
+
See the [`examples/`](examples/) directory for a runnable example per
|
|
113
|
+
provider:
|
|
114
|
+
|
|
115
|
+
| File | What it shows |
|
|
116
|
+
| ------------------------------------------------- | ------------------------------------------------ |
|
|
117
|
+
| [`telebirr.py`](examples/telebirr.py) | Verify a Telebirr receipt by reference number |
|
|
118
|
+
| [`cbe.py`](examples/cbe.py) | Fetch and parse a CBE PDF receipt |
|
|
119
|
+
| [`cbe_birr.py`](examples/cbe_birr.py) | Verify a CBE Birr wallet transaction |
|
|
120
|
+
| [`dashen.py`](examples/dashen.py) | Verify a Dashen Bank receipt with retry logic |
|
|
121
|
+
| [`abyssinia.py`](examples/abyssinia.py) | Verify a Bank of Abyssinia transaction |
|
|
122
|
+
| [`mpesa.py`](examples/mpesa.py) | Verify an Ethiopian M-Pesa transaction |
|
|
123
|
+
| [`image.py`](examples/image.py) | Analyse a receipt image with Mistral Vision AI |
|
|
124
|
+
| [`universal.py`](examples/universal.py) | Let the library auto-route to the right provider |
|
|
125
|
+
| [`error_handling.py`](examples/error_handling.py) | Catch provider-specific errors gracefully |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Provider Reference
|
|
130
|
+
|
|
131
|
+
### CBE — Commercial Bank of Ethiopia
|
|
132
|
+
|
|
133
|
+
CBE references are **12 characters** starting with `FT`. You must supply the
|
|
134
|
+
last **8 digits** of the account number as a suffix. The bank returns a PDF
|
|
135
|
+
that is fetched and parsed automatically.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from tx_verify import verify_cbe
|
|
139
|
+
|
|
140
|
+
result = await verify_cbe("FT23062669JJ", "12345678")
|
|
141
|
+
# result.success → bool
|
|
142
|
+
# result.payer → str | None
|
|
143
|
+
# result.receiver → str | None
|
|
144
|
+
# result.amount → float | None
|
|
145
|
+
# result.date → datetime | None
|
|
146
|
+
# result.reference → str | None
|
|
147
|
+
# result.reason → str | None
|
|
148
|
+
# result.error → str | None
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Telebirr
|
|
152
|
+
|
|
153
|
+
Telebirr references are **10-character alphanumeric** codes. The library scrapes
|
|
154
|
+
the public Ethio Telecom receipt page. It tries the primary source first,
|
|
155
|
+
then any fallback proxies configured via the `FALLBACK_PROXIES` environment
|
|
156
|
+
variable.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from tx_verify import verify_telebirr
|
|
160
|
+
|
|
161
|
+
receipt = await verify_telebirr("CE12345678")
|
|
162
|
+
# receipt.payer_name, receipt.settled_amount, receipt.total_paid_amount, …
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Dashen Bank
|
|
166
|
+
|
|
167
|
+
Dashen references are **16-digit numbers** starting with 3 digits (e.g.
|
|
168
|
+
`1234567890123456`). The verifier fetches a PDF with built-in retry logic
|
|
169
|
+
(up to 5 attempts).
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from tx_verify import verify_dashen
|
|
173
|
+
|
|
174
|
+
result = await verify_dashen("1234567890123456")
|
|
175
|
+
# result.sender_name, result.transaction_amount, result.total, …
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Bank of Abyssinia
|
|
179
|
+
|
|
180
|
+
Abyssinia references are also **12 characters** starting with `FT`, but the
|
|
181
|
+
suffix is the last **5 digits** of the account number. The bank returns JSON
|
|
182
|
+
rather than a PDF.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from tx_verify import verify_abyssinia
|
|
186
|
+
|
|
187
|
+
result = await verify_abyssinia("FT23062669JJ", "90172")
|
|
188
|
+
# result.payer, result.amount, result.date, …
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### CBE Birr
|
|
192
|
+
|
|
193
|
+
CBE Birr receipts are **10-character alphanumeric** codes. You also need the
|
|
194
|
+
wallet phone number in international format (`251…`).
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from tx_verify import verify_cbe_birr
|
|
198
|
+
|
|
199
|
+
result = await verify_cbe_birr("AB1234CD56", "251911234567")
|
|
200
|
+
# result.customer_name, result.amount, result.paid_amount, …
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### M-Pesa
|
|
204
|
+
|
|
205
|
+
M-Pesa references are **10-character alphanumeric** codes. The verifier hits
|
|
206
|
+
the Safaricom primary API first, then falls back to a proxy if configured via
|
|
207
|
+
`MPESA_PROXY_KEY`.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from tx_verify import verify_mpesa
|
|
211
|
+
|
|
212
|
+
result = await verify_mpesa("UE20VG1GS8")
|
|
213
|
+
# result.payer_name, result.amount, result.service_fee, result.vat, …
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Image verification (Mistral Vision)
|
|
217
|
+
|
|
218
|
+
Upload a receipt image (JPEG/PNG) and Mistral Vision AI will detect whether it
|
|
219
|
+
is a CBE or Telebirr receipt, extract the reference, and optionally verify it
|
|
220
|
+
automatically.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from tx_verify import verify_image
|
|
224
|
+
|
|
225
|
+
with open("receipt.jpg", "rb") as f:
|
|
226
|
+
image_bytes = f.read()
|
|
227
|
+
|
|
228
|
+
# Detect only
|
|
229
|
+
info = await verify_image(image_bytes, auto_verify=False)
|
|
230
|
+
print(info.type, info.reference, info.forward_to)
|
|
231
|
+
|
|
232
|
+
# Auto-verify (account_suffix required for CBE)
|
|
233
|
+
info = await verify_image(
|
|
234
|
+
image_bytes,
|
|
235
|
+
auto_verify=True,
|
|
236
|
+
account_suffix="12345678",
|
|
237
|
+
)
|
|
238
|
+
print(info.verified, info.details)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> Requires `MISTRAL_API_KEY` environment variable and the `mistralai` package
|
|
242
|
+
> (installed automatically).
|
|
243
|
+
|
|
244
|
+
### Universal — auto-route by reference format
|
|
245
|
+
|
|
246
|
+
Hand any reference to `verify_universal()` and it routes to the correct provider
|
|
247
|
+
based on length and prefix:
|
|
248
|
+
|
|
249
|
+
| Reference format | Routed to |
|
|
250
|
+
| -------------------------------------------- | ----------------- |
|
|
251
|
+
| 16 digits starting with `3` | Dashen Bank |
|
|
252
|
+
| 12 chars starting with `FT` + 8-digit suffix | CBE |
|
|
253
|
+
| 12 chars starting with `FT` + 5-digit suffix | Bank of Abyssinia |
|
|
254
|
+
| 10 chars + `phone_number` | CBE Birr |
|
|
255
|
+
| 10 chars (no phone) | Telebirr |
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from tx_verify import verify_universal
|
|
259
|
+
|
|
260
|
+
result = await verify_universal("CE12345678")
|
|
261
|
+
print(result.success, result.data, result.error)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Proxy Support
|
|
267
|
+
|
|
268
|
+
All receipt verifiers accept an explicit ``proxies`` argument. **Environment
|
|
269
|
+
variables are never read automatically** — you must pass the proxy yourself.
|
|
270
|
+
|
|
271
|
+
Supported schemes:
|
|
272
|
+
|
|
273
|
+
| Scheme | Description |
|
|
274
|
+
| -------- | ---------------------------------------------- |
|
|
275
|
+
| `http` | Plain HTTP forward proxy |
|
|
276
|
+
| `https` | HTTPS proxy (CONNECT tunnel) |
|
|
277
|
+
| `socks4` | SOCKS4 proxy |
|
|
278
|
+
| `socks5` | SOCKS5 proxy (client resolves DNS) |
|
|
279
|
+
| `socks5h`| SOCKS5 proxy (proxy resolves DNS) |
|
|
280
|
+
|
|
281
|
+
Authentication is embedded in the URL:
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
# Single global proxy
|
|
285
|
+
proxies = "http://user:pass@proxy.example.com:8080"
|
|
286
|
+
|
|
287
|
+
# Per-scheme mapping
|
|
288
|
+
proxies = {
|
|
289
|
+
"http://": "http://proxy.example.com:8080",
|
|
290
|
+
"https://": "socks5://localhost:1080",
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Pass it to any verifier:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from tx_verify import verify_telebirr, verify_cbe, verify_mpesa
|
|
298
|
+
|
|
299
|
+
# Telebirr through an HTTP proxy
|
|
300
|
+
receipt = await verify_telebirr("CE12345678", proxies="http://proxy:8080")
|
|
301
|
+
|
|
302
|
+
# CBE through SOCKS5
|
|
303
|
+
result = await verify_cbe("FT23062669JJ", "12345678", proxies="socks5://127.0.0.1:1080")
|
|
304
|
+
|
|
305
|
+
# M-Pesa with per-scheme mapping
|
|
306
|
+
result = await verify_mpesa("UE20VG1GS8", proxies={
|
|
307
|
+
"http://": "http://proxy:8080",
|
|
308
|
+
"https://": "socks5h://proxy:1080",
|
|
309
|
+
})
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`verify_universal` and `verify_image` also forward ``proxies`` to the
|
|
313
|
+
underlying provider automatically.
|
|
314
|
+
|
|
315
|
+
> **SOCKS tip:** `socks5h://` tells the proxy server to resolve hostnames,
|
|
316
|
+
> which is useful when the client cannot reach DNS directly.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Error Handling
|
|
321
|
+
|
|
322
|
+
All verifiers return **result objects** rather than raising for expected
|
|
323
|
+
failures (network errors, missing receipts, parsing failures). Inspect
|
|
324
|
+
`result.success` and `result.error`.
|
|
325
|
+
|
|
326
|
+
Telebirr may raise `TelebirrVerificationError` when a proxy returns an explicit
|
|
327
|
+
error message. Catch it if you want to show the user a friendly message:
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from tx_verify import TelebirrVerificationError, verify_telebirr
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
receipt = await verify_telebirr("INVALID_REF")
|
|
334
|
+
except TelebirrVerificationError as exc:
|
|
335
|
+
print(f"Telebirr error: {exc}")
|
|
336
|
+
if exc.details:
|
|
337
|
+
print(f"Details: {exc.details}")
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The library also provides a generic error handler for wrapping database or
|
|
341
|
+
internal errors:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from tx_verify.utils.error_handler import AppError, ErrorType
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Environment Variables
|
|
350
|
+
|
|
351
|
+
| Variable | Purpose |
|
|
352
|
+
| --------------------------- | -------------------------------------------------------- |
|
|
353
|
+
| `FALLBACK_PROXIES` | Comma-separated proxy URLs for Telebirr |
|
|
354
|
+
| `TELEBIRR_PROXY_KEY` | API key for Telebirr proxy endpoints |
|
|
355
|
+
| `SKIP_PRIMARY_VERIFICATION` | Set to `true` to skip primary source (Telebirr / M-Pesa) |
|
|
356
|
+
| `MPESA_PROXY_KEY` | API key for M-Pesa fallback proxy |
|
|
357
|
+
| `MISTRAL_API_KEY` | Required for `verify_image()` |
|
|
358
|
+
| `LOG_LEVEL` | `DEBUG` or `INFO` (default `INFO`) |
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Development
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Clone
|
|
366
|
+
git clone https://github.com/YOUR_USERNAME/tx-verify.git
|
|
367
|
+
cd tx-verify
|
|
368
|
+
|
|
369
|
+
# Install with dev dependencies
|
|
370
|
+
pip install -e ".[dev]"
|
|
371
|
+
|
|
372
|
+
# Lint & format
|
|
373
|
+
ruff check .
|
|
374
|
+
ruff format .
|
|
375
|
+
|
|
376
|
+
# Type-check
|
|
377
|
+
mypy tx_verify/
|
|
378
|
+
|
|
379
|
+
# Run tests
|
|
380
|
+
pytest
|
|
381
|
+
```
|