zayono 1.0.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.
- zayono-1.0.0/.gitignore +20 -0
- zayono-1.0.0/LICENSE +21 -0
- zayono-1.0.0/PKG-INFO +267 -0
- zayono-1.0.0/README.md +225 -0
- zayono-1.0.0/docs/checkout.md +39 -0
- zayono-1.0.0/docs/customers.md +27 -0
- zayono-1.0.0/docs/payments.md +65 -0
- zayono-1.0.0/docs/payouts.md +48 -0
- zayono-1.0.0/docs/webhooks.md +68 -0
- zayono-1.0.0/pyproject.toml +97 -0
- zayono-1.0.0/src/zayono/__init__.py +56 -0
- zayono-1.0.0/src/zayono/_http.py +553 -0
- zayono-1.0.0/src/zayono/_version.py +8 -0
- zayono-1.0.0/src/zayono/async_client.py +117 -0
- zayono-1.0.0/src/zayono/client.py +148 -0
- zayono-1.0.0/src/zayono/exceptions.py +144 -0
- zayono-1.0.0/src/zayono/idempotency.py +29 -0
- zayono-1.0.0/src/zayono/pagination.py +237 -0
- zayono-1.0.0/src/zayono/py.typed +0 -0
- zayono-1.0.0/src/zayono/resources/__init__.py +24 -0
- zayono-1.0.0/src/zayono/resources/checkout.py +141 -0
- zayono-1.0.0/src/zayono/resources/customers.py +185 -0
- zayono-1.0.0/src/zayono/resources/payments.py +404 -0
- zayono-1.0.0/src/zayono/resources/payouts.py +364 -0
- zayono-1.0.0/src/zayono/types.py +180 -0
- zayono-1.0.0/src/zayono/webhooks.py +117 -0
- zayono-1.0.0/tests/__init__.py +0 -0
- zayono-1.0.0/tests/conftest.py +78 -0
- zayono-1.0.0/tests/test_async_client.py +207 -0
- zayono-1.0.0/tests/test_audit_fixes.py +182 -0
- zayono-1.0.0/tests/test_client.py +394 -0
- zayono-1.0.0/tests/test_pagination.py +223 -0
- zayono-1.0.0/tests/test_retries.py +181 -0
- zayono-1.0.0/tests/test_webhooks.py +83 -0
zayono-1.0.0/.gitignore
ADDED
zayono-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zayono
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
zayono-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zayono
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for the Zayono unified Mobile Money payment gateway.
|
|
5
|
+
Project-URL: Homepage, https://zayono.com
|
|
6
|
+
Project-URL: Documentation, https://docs.zayono.com/sdks/python
|
|
7
|
+
Project-URL: Repository, https://github.com/RomualdAKM/sdks-zayono/tree/main/zayono-python
|
|
8
|
+
Project-URL: Issues, https://github.com/RomualdAKM/sdks-zayono/issues
|
|
9
|
+
Author-email: Zayono <developers@zayono.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: africa,asyncio,httpx,mobile-money,payment-gateway,payments,sdk,zayono
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Requires-Dist: httpx<1,>=0.25
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.7; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
36
|
+
Requires-Dist: respx>=0.20; extra == 'dev'
|
|
37
|
+
Provides-Extra: test
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
|
|
39
|
+
Requires-Dist: pytest>=7.4; extra == 'test'
|
|
40
|
+
Requires-Dist: respx>=0.20; extra == 'test'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# Zayono Python SDK
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/zayono/)
|
|
46
|
+
[](https://pypi.org/project/zayono/)
|
|
47
|
+
[](https://github.com/RomualdAKM/sdks-zayono/blob/main/zayono-python/LICENSE)
|
|
48
|
+
|
|
49
|
+
Official Python SDK for the [Zayono](https://zayono.com) unified Mobile Money
|
|
50
|
+
payment gateway for Africa.
|
|
51
|
+
|
|
52
|
+
- **Sync + async** out of the box: `Zayono` and `AsyncZayono`.
|
|
53
|
+
- **Type-safe**: ships `py.typed`; complete type hints picked up by mypy/pyright/Pylance.
|
|
54
|
+
- **Resilient**: automatic retries (429/5xx/connect) with jittered exponential backoff,
|
|
55
|
+
honours `Retry-After`.
|
|
56
|
+
- **Idempotent by default**: every mutating request carries an auto-generated
|
|
57
|
+
`X-Idempotency-Key` (UUIDv4); override with `idempotency_key=`.
|
|
58
|
+
- **Framework-agnostic**: integrates with Django, Flask, FastAPI, Starlette, etc.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install zayono
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.9+.
|
|
67
|
+
|
|
68
|
+
## Quick start (sync)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import os
|
|
72
|
+
from zayono import Zayono
|
|
73
|
+
|
|
74
|
+
client = Zayono(api_key=os.environ["ZAYONO_API_KEY"])
|
|
75
|
+
|
|
76
|
+
payment = client.payments.create(
|
|
77
|
+
amount=5000,
|
|
78
|
+
currency="XOF",
|
|
79
|
+
description="Premium t-shirt",
|
|
80
|
+
customer={
|
|
81
|
+
"email": "customer@example.com",
|
|
82
|
+
"first_name": "Jean",
|
|
83
|
+
"last_name": "Dupont",
|
|
84
|
+
},
|
|
85
|
+
return_url="https://example.com/success",
|
|
86
|
+
metadata={"order_id": "ORD-12345"},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
print(payment["checkout_url"])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Quick start (async)
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from zayono import AsyncZayono
|
|
97
|
+
|
|
98
|
+
async def main():
|
|
99
|
+
async with AsyncZayono(api_key="zyn_test_xxxxx") as client:
|
|
100
|
+
payment = await client.payments.create(
|
|
101
|
+
amount=5000,
|
|
102
|
+
currency="XOF",
|
|
103
|
+
description="Premium t-shirt",
|
|
104
|
+
return_url="https://example.com/success",
|
|
105
|
+
customer={
|
|
106
|
+
"email": "c@example.com",
|
|
107
|
+
"first_name": "Jean",
|
|
108
|
+
"last_name": "Dupont",
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
print(payment["checkout_url"])
|
|
112
|
+
|
|
113
|
+
asyncio.run(main())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
### 1. Initiate a payment with a specific operator (no hosted page)
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from zayono import Zayono
|
|
122
|
+
|
|
123
|
+
client = Zayono(api_key="zyn_test_xxxxx")
|
|
124
|
+
|
|
125
|
+
payment = client.payments.create(
|
|
126
|
+
amount=15000,
|
|
127
|
+
currency="XOF",
|
|
128
|
+
operator="mtn_bj",
|
|
129
|
+
description="Plan Pro mensuel",
|
|
130
|
+
return_url="https://example.com/success",
|
|
131
|
+
customer={
|
|
132
|
+
"email": "c@example.com",
|
|
133
|
+
"first_name": "Jean",
|
|
134
|
+
"last_name": "Dupont",
|
|
135
|
+
"phone": "+22961000000",
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
# Payment is dispatched immediately. Poll its status via retrieve()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2. Initiate a payout (disbursement), async
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import asyncio
|
|
145
|
+
from zayono import AsyncZayono
|
|
146
|
+
|
|
147
|
+
async def disburse():
|
|
148
|
+
async with AsyncZayono(api_key="zyn_test_xxxxx") as client:
|
|
149
|
+
payout = await client.payouts.create(
|
|
150
|
+
amount=20000,
|
|
151
|
+
currency="XOF",
|
|
152
|
+
operator="moov_bj",
|
|
153
|
+
recipient={
|
|
154
|
+
"phone": "+22961000000",
|
|
155
|
+
"first_name": "Kossi",
|
|
156
|
+
"last_name": "Mensah",
|
|
157
|
+
},
|
|
158
|
+
description="Payout for order ORD-12345",
|
|
159
|
+
)
|
|
160
|
+
print(payout["id"], payout["status"])
|
|
161
|
+
|
|
162
|
+
asyncio.run(disburse())
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 3. Stream every successful payment (auto-paginated)
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from zayono import Zayono
|
|
169
|
+
|
|
170
|
+
client = Zayono(api_key="zyn_test_xxxxx")
|
|
171
|
+
|
|
172
|
+
for payment in client.payments.list(status="success", per_page=100):
|
|
173
|
+
print(payment["id"], payment["amount"], payment["currency"])
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Async equivalent:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
async for payment in async_client.payments.list(status="success"):
|
|
180
|
+
print(payment["id"])
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. Verify a webhook signature (FastAPI)
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
187
|
+
from zayono import Zayono
|
|
188
|
+
import os
|
|
189
|
+
|
|
190
|
+
app = FastAPI()
|
|
191
|
+
client = Zayono(api_key=os.environ["ZAYONO_API_KEY"])
|
|
192
|
+
|
|
193
|
+
@app.post("/webhooks/zayono")
|
|
194
|
+
async def webhook(request: Request):
|
|
195
|
+
body = await request.body() # raw bytes, required
|
|
196
|
+
signature = request.headers.get("x-zayono-signature", "")
|
|
197
|
+
secret = os.environ["ZAYONO_WEBHOOK_SECRET"]
|
|
198
|
+
|
|
199
|
+
if not client.webhooks.verify(body, signature, secret):
|
|
200
|
+
raise HTTPException(401, "Invalid signature")
|
|
201
|
+
|
|
202
|
+
event = await request.json()
|
|
203
|
+
# ... handle event["type"], event["data"]
|
|
204
|
+
return {"received": True}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
> **Refunds**: the public API-key surface does not yet expose
|
|
208
|
+
> `POST /v1/payments/{id}/refunds`. For now, trigger refunds from the
|
|
209
|
+
> Zayono merchant dashboard. A `refunds` resource will be added to this
|
|
210
|
+
> SDK once the endpoint ships on the v1 surface.
|
|
211
|
+
|
|
212
|
+
## Error handling
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from zayono.exceptions import (
|
|
216
|
+
ValidationError, AuthenticationError, RateLimitError,
|
|
217
|
+
NotFoundError, ServerError, NetworkError,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
payment = client.payments.create(amount=-1, currency="XOF")
|
|
222
|
+
except ValidationError as e:
|
|
223
|
+
# 422: e.errors is dict[str, list[str]]
|
|
224
|
+
for field, messages in e.errors.items():
|
|
225
|
+
print(f"{field}: {', '.join(messages)}")
|
|
226
|
+
except AuthenticationError:
|
|
227
|
+
# 401 / 403
|
|
228
|
+
...
|
|
229
|
+
except RateLimitError as e:
|
|
230
|
+
# 429: e.retry_after is in seconds, may be None
|
|
231
|
+
...
|
|
232
|
+
except NotFoundError:
|
|
233
|
+
# 404
|
|
234
|
+
...
|
|
235
|
+
except ServerError:
|
|
236
|
+
# 5xx after retries exhausted
|
|
237
|
+
...
|
|
238
|
+
except NetworkError:
|
|
239
|
+
# Transport-level failure (timeout, connect refused, DNS)
|
|
240
|
+
...
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Configuration
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
client = Zayono(
|
|
247
|
+
api_key="zyn_test_xxxxx",
|
|
248
|
+
base_url="https://backend.zayono.com/api/v1", # default
|
|
249
|
+
timeout=30.0, # seconds, default 30
|
|
250
|
+
max_retries=3, # default 3
|
|
251
|
+
application_id=None, # only needed for dashboard endpoints
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Development
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
git clone https://github.com/RomualdAKM/sdks-zayono
|
|
259
|
+
cd sdks-zayono/zayono-python
|
|
260
|
+
pip install -e ".[test]"
|
|
261
|
+
pytest # run the test suite
|
|
262
|
+
python -m build # produce wheel + sdist into dist/
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT. See [LICENSE](https://github.com/RomualdAKM/sdks-zayono/blob/main/zayono-python/LICENSE).
|
zayono-1.0.0/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Zayono Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/zayono/)
|
|
4
|
+
[](https://pypi.org/project/zayono/)
|
|
5
|
+
[](https://github.com/RomualdAKM/sdks-zayono/blob/main/zayono-python/LICENSE)
|
|
6
|
+
|
|
7
|
+
Official Python SDK for the [Zayono](https://zayono.com) unified Mobile Money
|
|
8
|
+
payment gateway for Africa.
|
|
9
|
+
|
|
10
|
+
- **Sync + async** out of the box: `Zayono` and `AsyncZayono`.
|
|
11
|
+
- **Type-safe**: ships `py.typed`; complete type hints picked up by mypy/pyright/Pylance.
|
|
12
|
+
- **Resilient**: automatic retries (429/5xx/connect) with jittered exponential backoff,
|
|
13
|
+
honours `Retry-After`.
|
|
14
|
+
- **Idempotent by default**: every mutating request carries an auto-generated
|
|
15
|
+
`X-Idempotency-Key` (UUIDv4); override with `idempotency_key=`.
|
|
16
|
+
- **Framework-agnostic**: integrates with Django, Flask, FastAPI, Starlette, etc.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install zayono
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Python 3.9+.
|
|
25
|
+
|
|
26
|
+
## Quick start (sync)
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import os
|
|
30
|
+
from zayono import Zayono
|
|
31
|
+
|
|
32
|
+
client = Zayono(api_key=os.environ["ZAYONO_API_KEY"])
|
|
33
|
+
|
|
34
|
+
payment = client.payments.create(
|
|
35
|
+
amount=5000,
|
|
36
|
+
currency="XOF",
|
|
37
|
+
description="Premium t-shirt",
|
|
38
|
+
customer={
|
|
39
|
+
"email": "customer@example.com",
|
|
40
|
+
"first_name": "Jean",
|
|
41
|
+
"last_name": "Dupont",
|
|
42
|
+
},
|
|
43
|
+
return_url="https://example.com/success",
|
|
44
|
+
metadata={"order_id": "ORD-12345"},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
print(payment["checkout_url"])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick start (async)
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import asyncio
|
|
54
|
+
from zayono import AsyncZayono
|
|
55
|
+
|
|
56
|
+
async def main():
|
|
57
|
+
async with AsyncZayono(api_key="zyn_test_xxxxx") as client:
|
|
58
|
+
payment = await client.payments.create(
|
|
59
|
+
amount=5000,
|
|
60
|
+
currency="XOF",
|
|
61
|
+
description="Premium t-shirt",
|
|
62
|
+
return_url="https://example.com/success",
|
|
63
|
+
customer={
|
|
64
|
+
"email": "c@example.com",
|
|
65
|
+
"first_name": "Jean",
|
|
66
|
+
"last_name": "Dupont",
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
print(payment["checkout_url"])
|
|
70
|
+
|
|
71
|
+
asyncio.run(main())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
|
|
76
|
+
### 1. Initiate a payment with a specific operator (no hosted page)
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from zayono import Zayono
|
|
80
|
+
|
|
81
|
+
client = Zayono(api_key="zyn_test_xxxxx")
|
|
82
|
+
|
|
83
|
+
payment = client.payments.create(
|
|
84
|
+
amount=15000,
|
|
85
|
+
currency="XOF",
|
|
86
|
+
operator="mtn_bj",
|
|
87
|
+
description="Plan Pro mensuel",
|
|
88
|
+
return_url="https://example.com/success",
|
|
89
|
+
customer={
|
|
90
|
+
"email": "c@example.com",
|
|
91
|
+
"first_name": "Jean",
|
|
92
|
+
"last_name": "Dupont",
|
|
93
|
+
"phone": "+22961000000",
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
# Payment is dispatched immediately. Poll its status via retrieve()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Initiate a payout (disbursement), async
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import asyncio
|
|
103
|
+
from zayono import AsyncZayono
|
|
104
|
+
|
|
105
|
+
async def disburse():
|
|
106
|
+
async with AsyncZayono(api_key="zyn_test_xxxxx") as client:
|
|
107
|
+
payout = await client.payouts.create(
|
|
108
|
+
amount=20000,
|
|
109
|
+
currency="XOF",
|
|
110
|
+
operator="moov_bj",
|
|
111
|
+
recipient={
|
|
112
|
+
"phone": "+22961000000",
|
|
113
|
+
"first_name": "Kossi",
|
|
114
|
+
"last_name": "Mensah",
|
|
115
|
+
},
|
|
116
|
+
description="Payout for order ORD-12345",
|
|
117
|
+
)
|
|
118
|
+
print(payout["id"], payout["status"])
|
|
119
|
+
|
|
120
|
+
asyncio.run(disburse())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Stream every successful payment (auto-paginated)
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from zayono import Zayono
|
|
127
|
+
|
|
128
|
+
client = Zayono(api_key="zyn_test_xxxxx")
|
|
129
|
+
|
|
130
|
+
for payment in client.payments.list(status="success", per_page=100):
|
|
131
|
+
print(payment["id"], payment["amount"], payment["currency"])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Async equivalent:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
async for payment in async_client.payments.list(status="success"):
|
|
138
|
+
print(payment["id"])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 4. Verify a webhook signature (FastAPI)
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
145
|
+
from zayono import Zayono
|
|
146
|
+
import os
|
|
147
|
+
|
|
148
|
+
app = FastAPI()
|
|
149
|
+
client = Zayono(api_key=os.environ["ZAYONO_API_KEY"])
|
|
150
|
+
|
|
151
|
+
@app.post("/webhooks/zayono")
|
|
152
|
+
async def webhook(request: Request):
|
|
153
|
+
body = await request.body() # raw bytes, required
|
|
154
|
+
signature = request.headers.get("x-zayono-signature", "")
|
|
155
|
+
secret = os.environ["ZAYONO_WEBHOOK_SECRET"]
|
|
156
|
+
|
|
157
|
+
if not client.webhooks.verify(body, signature, secret):
|
|
158
|
+
raise HTTPException(401, "Invalid signature")
|
|
159
|
+
|
|
160
|
+
event = await request.json()
|
|
161
|
+
# ... handle event["type"], event["data"]
|
|
162
|
+
return {"received": True}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
> **Refunds**: the public API-key surface does not yet expose
|
|
166
|
+
> `POST /v1/payments/{id}/refunds`. For now, trigger refunds from the
|
|
167
|
+
> Zayono merchant dashboard. A `refunds` resource will be added to this
|
|
168
|
+
> SDK once the endpoint ships on the v1 surface.
|
|
169
|
+
|
|
170
|
+
## Error handling
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from zayono.exceptions import (
|
|
174
|
+
ValidationError, AuthenticationError, RateLimitError,
|
|
175
|
+
NotFoundError, ServerError, NetworkError,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
payment = client.payments.create(amount=-1, currency="XOF")
|
|
180
|
+
except ValidationError as e:
|
|
181
|
+
# 422: e.errors is dict[str, list[str]]
|
|
182
|
+
for field, messages in e.errors.items():
|
|
183
|
+
print(f"{field}: {', '.join(messages)}")
|
|
184
|
+
except AuthenticationError:
|
|
185
|
+
# 401 / 403
|
|
186
|
+
...
|
|
187
|
+
except RateLimitError as e:
|
|
188
|
+
# 429: e.retry_after is in seconds, may be None
|
|
189
|
+
...
|
|
190
|
+
except NotFoundError:
|
|
191
|
+
# 404
|
|
192
|
+
...
|
|
193
|
+
except ServerError:
|
|
194
|
+
# 5xx after retries exhausted
|
|
195
|
+
...
|
|
196
|
+
except NetworkError:
|
|
197
|
+
# Transport-level failure (timeout, connect refused, DNS)
|
|
198
|
+
...
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Configuration
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
client = Zayono(
|
|
205
|
+
api_key="zyn_test_xxxxx",
|
|
206
|
+
base_url="https://backend.zayono.com/api/v1", # default
|
|
207
|
+
timeout=30.0, # seconds, default 30
|
|
208
|
+
max_retries=3, # default 3
|
|
209
|
+
application_id=None, # only needed for dashboard endpoints
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
git clone https://github.com/RomualdAKM/sdks-zayono
|
|
217
|
+
cd sdks-zayono/zayono-python
|
|
218
|
+
pip install -e ".[test]"
|
|
219
|
+
pytest # run the test suite
|
|
220
|
+
python -m build # produce wheel + sdist into dist/
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT. See [LICENSE](https://github.com/RomualdAKM/sdks-zayono/blob/main/zayono-python/LICENSE).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# `checkout` resource
|
|
2
|
+
|
|
3
|
+
`POST /v1/checkout/initialize`.
|
|
4
|
+
|
|
5
|
+
## `checkout.initialize(...)`
|
|
6
|
+
|
|
7
|
+
Create a hosted-checkout session and receive a `checkout_url` to redirect
|
|
8
|
+
the customer to. The session collects the operator + phone on the merchant's
|
|
9
|
+
behalf, the SDK is a thin pass-through.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
session = client.checkout.initialize(
|
|
13
|
+
amount=12500,
|
|
14
|
+
currency="XOF",
|
|
15
|
+
description="Premium subscription",
|
|
16
|
+
customer_email="c@example.com",
|
|
17
|
+
return_url="https://example.com/thanks",
|
|
18
|
+
cancel_url="https://example.com/cart",
|
|
19
|
+
metadata={"order_id": "ORD-789"},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Redirect the customer:
|
|
23
|
+
print(session["checkout_url"])
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Parameter | Type | Required | Notes |
|
|
27
|
+
|-------------------|--------------------------|----------|------------------------------------|
|
|
28
|
+
| `amount` | `int` | yes | Minor units. |
|
|
29
|
+
| `currency` | `str` | yes | ISO-4217. |
|
|
30
|
+
| `description` | `str \| None` | no | |
|
|
31
|
+
| `customer_email` | `str \| None` | no | |
|
|
32
|
+
| `customer_name` | `str \| None` | no | |
|
|
33
|
+
| `customer_id` | `str \| None` | no | |
|
|
34
|
+
| `return_url` | `str \| None` | no | |
|
|
35
|
+
| `cancel_url` | `str \| None` | no | |
|
|
36
|
+
| `callback_url` | `str \| None` | no | |
|
|
37
|
+
| `metadata` | `dict[str, Any] \| None` | no | |
|
|
38
|
+
| `idempotency_key` | `str \| None` | no | |
|
|
39
|
+
| `extra` | `dict[str, Any] \| None` | no | Forward-compatible escape hatch. |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# `customers` resource
|
|
2
|
+
|
|
3
|
+
`GET /v1/customers/{id}`, `GET /v1/customers`.
|
|
4
|
+
|
|
5
|
+
Customer rows are created implicitly when you initiate a payment with a new
|
|
6
|
+
`customer_email`. The endpoints are read-only, there is no
|
|
7
|
+
`customers.create(...)`.
|
|
8
|
+
|
|
9
|
+
## `customers.retrieve(customer_id)`
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
customer = client.customers.retrieve("019e5eaf-cb99-7351-a6d5-c219e28534db")
|
|
13
|
+
print(customer["email"], customer["total_payments"])
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## `customers.list(...)` / `async for customer in ...`
|
|
17
|
+
|
|
18
|
+
Paginator over every customer. Filters:
|
|
19
|
+
|
|
20
|
+
- `email`, `phone`, `country`
|
|
21
|
+
- `search` (substring match against `email`, `name`, `phone`)
|
|
22
|
+
- `per_page` (default 20)
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
for c in client.customers.list(country="BJ"):
|
|
26
|
+
print(c["id"], c["email"])
|
|
27
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# `payments` resource
|
|
2
|
+
|
|
3
|
+
`POST /v1/payments/initialize`, `GET /v1/payments/{id}`, `GET /v1/payments`.
|
|
4
|
+
|
|
5
|
+
## `payments.create(...)` / `await async_client.payments.create(...)`
|
|
6
|
+
|
|
7
|
+
Initialise a payment. Returns the created payment resource as a dict
|
|
8
|
+
(the `data` field of the API envelope).
|
|
9
|
+
|
|
10
|
+
| Parameter | Type | Required | Notes |
|
|
11
|
+
|-------------------|---------------------------------|----------|----------------------------------------------------------------------------------------|
|
|
12
|
+
| `amount` | `int` | yes | Amount in **minor units** of `currency` (e.g. 500 = 5.00 XOF for an integer currency). |
|
|
13
|
+
| `currency` | `str` | yes | ISO-4217 code: `XOF`, `XAF`, `NGN`, `GHS`, `KES`, etc. |
|
|
14
|
+
| `description` | `str \| None` | no | Free-form merchant-side description. |
|
|
15
|
+
| `customer_email` | `str \| None` | no | Required by some aggregators for receipts. |
|
|
16
|
+
| `customer_name` | `str \| None` | no | |
|
|
17
|
+
| `customer_id` | `str \| None` | no | Pre-existing Zayono customer UUID, links the payment to that record. |
|
|
18
|
+
| `phone` | `str \| None` | no | E.164 (`+22961000000`). Mandatory when `operator` is set. |
|
|
19
|
+
| `operator` | `str \| None` | no | If set, dispatches immediately (`mtn_bj`, `moov_bj`, `orange_ci`, …). |
|
|
20
|
+
| `return_url` | `str \| None` | no | Where to redirect after success on the hosted page. |
|
|
21
|
+
| `cancel_url` | `str \| None` | no | Where to redirect after cancel. |
|
|
22
|
+
| `callback_url` | `str \| None` | no | Per-payment webhook override. |
|
|
23
|
+
| `metadata` | `dict[str, Any] \| None` | no | Echoed back on webhooks and `retrieve()`. |
|
|
24
|
+
| `idempotency_key` | `str \| None` | no | Override the auto-generated UUIDv4. |
|
|
25
|
+
| `extra` | `dict[str, Any] \| None` | no | Escape hatch for forward-compatible fields. |
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
payment = client.payments.create(
|
|
29
|
+
amount=5000,
|
|
30
|
+
currency="XOF",
|
|
31
|
+
customer_email="c@example.com",
|
|
32
|
+
return_url="https://example.com/success",
|
|
33
|
+
metadata={"order_id": "ORD-12345"},
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## `payments.retrieve(payment_id)` / `await ...`
|
|
38
|
+
|
|
39
|
+
Fetch a payment by id.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
payment = client.payments.retrieve("019e5eaf-cb99-7351-a6d5-c219e28534db")
|
|
43
|
+
if payment["status"] == "success":
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## `payments.list(...)` / `async for ... in async_client.payments.list(...)`
|
|
48
|
+
|
|
49
|
+
Returns a paginator (sync or async). Pagination is fully transparent, the
|
|
50
|
+
SDK fetches the next page lazily until the result set is exhausted.
|
|
51
|
+
|
|
52
|
+
Filters:
|
|
53
|
+
|
|
54
|
+
- `status`: `"pending" | "processing" | "success" | "failed" | "cancelled" | "expired" | "refunded" | "partially_refunded"`
|
|
55
|
+
- `customer_id`, `operator`, `currency`
|
|
56
|
+
- `from_date`, `to_date` (ISO-8601)
|
|
57
|
+
- `per_page` (1–100, default 20)
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
for payment in client.payments.list(status="success", per_page=50):
|
|
61
|
+
...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For a single page with pagination metadata, use `list_page(...)` and inspect
|
|
65
|
+
the returned envelope.
|