bazik-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.
- bazik_sdk-0.1.0/.gitignore +10 -0
- bazik_sdk-0.1.0/LICENSE +21 -0
- bazik_sdk-0.1.0/PKG-INFO +83 -0
- bazik_sdk-0.1.0/README.md +69 -0
- bazik_sdk-0.1.0/bazik/__init__.py +6 -0
- bazik_sdk-0.1.0/bazik/client.py +120 -0
- bazik_sdk-0.1.0/bazik/errors.py +10 -0
- bazik_sdk-0.1.0/pyproject.toml +21 -0
bazik_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Falandy Jean
|
|
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.
|
bazik_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bazik-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async Python SDK for the Bazik API — MonCash payments for Haiti.
|
|
5
|
+
Project-URL: Homepage, https://github.com/FalandyJEAN/bazik-sdk-python
|
|
6
|
+
Project-URL: Issues, https://github.com/FalandyJEAN/bazik-sdk-python/issues
|
|
7
|
+
Author: Falandy Jean
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bazik,haiti,moncash,natcash,payments
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: httpx>=0.24
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# bazik-sdk (Python)
|
|
16
|
+
|
|
17
|
+
Tiny **async** Python client for the [Bazik](https://bazik.io) API — **MonCash** payments for Haiti. Production-tested (powers payments on TradeMakaya). One dependency: `httpx`.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install bazik-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
from bazik import BazikClient
|
|
30
|
+
|
|
31
|
+
async def main():
|
|
32
|
+
client = BazikClient(user_id="YOUR_USER_ID", secret_key="YOUR_SECRET_KEY")
|
|
33
|
+
|
|
34
|
+
# 1) Create a payment (amount in gourdes, max 75,000 HTG)
|
|
35
|
+
order = await client.create_moncash_payment(
|
|
36
|
+
gdes=500,
|
|
37
|
+
reference_id="INV-1001",
|
|
38
|
+
description="Pro plan",
|
|
39
|
+
customer_first_name="Jean",
|
|
40
|
+
customer_last_name="Pierre",
|
|
41
|
+
customer_email="jean@example.com",
|
|
42
|
+
webhook_url="https://api.yoursite.com/billing/moncash/webhook",
|
|
43
|
+
success_url="https://yoursite.com/paid",
|
|
44
|
+
error_url="https://yoursite.com/canceled",
|
|
45
|
+
)
|
|
46
|
+
print("Send the customer to:", order["redirectUrl"])
|
|
47
|
+
|
|
48
|
+
# 2) Check status (or poll until it resolves)
|
|
49
|
+
result = await client.wait_for_completion(order["orderId"], interval=3, timeout=180)
|
|
50
|
+
print("Status:", result["status"]) # "successful" | "pending" | "failed"
|
|
51
|
+
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
| Method | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `BazikClient(user_id, secret_key, base_url=…, timeout=20)` | Create a client. |
|
|
60
|
+
| `await authenticate()` | Fetch & cache a bearer token (auto-called). |
|
|
61
|
+
| `await create_moncash_payment(*, gdes, reference_id, …)` | Create a MonCash payment → `{orderId, redirectUrl, …}`. |
|
|
62
|
+
| `await verify_payment(order_id)` | Current status → `{status, referenceId, …}`. |
|
|
63
|
+
| `await wait_for_completion(order_id, *, interval=3, timeout=180)` | Poll until resolved. |
|
|
64
|
+
|
|
65
|
+
`create_moncash_payment` optional fields: `webhook_url`, `description`, `customer_first_name`, `customer_last_name`, `customer_email`, `success_url`, `error_url`.
|
|
66
|
+
|
|
67
|
+
Errors raise `bazik.BazikError` (`.status_code`, `.body`).
|
|
68
|
+
|
|
69
|
+
> **Note:** the amount field is `gdes` (gourdes) — not `gourdes`. Sending the wrong key creates a payment with no amount.
|
|
70
|
+
|
|
71
|
+
## Webhook flow
|
|
72
|
+
|
|
73
|
+
Bazik POSTs `{ orderId }` to your `webhook_url` on a status change. **Don't trust the body** — verify:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
verified = await client.verify_payment(order_id)
|
|
77
|
+
if verified["status"] == "successful":
|
|
78
|
+
... # activate the order
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT © Falandy Jean. Unofficial SDK; not affiliated with Bazik.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# bazik-sdk (Python)
|
|
2
|
+
|
|
3
|
+
Tiny **async** Python client for the [Bazik](https://bazik.io) API — **MonCash** payments for Haiti. Production-tested (powers payments on TradeMakaya). One dependency: `httpx`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install bazik-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import asyncio
|
|
15
|
+
from bazik import BazikClient
|
|
16
|
+
|
|
17
|
+
async def main():
|
|
18
|
+
client = BazikClient(user_id="YOUR_USER_ID", secret_key="YOUR_SECRET_KEY")
|
|
19
|
+
|
|
20
|
+
# 1) Create a payment (amount in gourdes, max 75,000 HTG)
|
|
21
|
+
order = await client.create_moncash_payment(
|
|
22
|
+
gdes=500,
|
|
23
|
+
reference_id="INV-1001",
|
|
24
|
+
description="Pro plan",
|
|
25
|
+
customer_first_name="Jean",
|
|
26
|
+
customer_last_name="Pierre",
|
|
27
|
+
customer_email="jean@example.com",
|
|
28
|
+
webhook_url="https://api.yoursite.com/billing/moncash/webhook",
|
|
29
|
+
success_url="https://yoursite.com/paid",
|
|
30
|
+
error_url="https://yoursite.com/canceled",
|
|
31
|
+
)
|
|
32
|
+
print("Send the customer to:", order["redirectUrl"])
|
|
33
|
+
|
|
34
|
+
# 2) Check status (or poll until it resolves)
|
|
35
|
+
result = await client.wait_for_completion(order["orderId"], interval=3, timeout=180)
|
|
36
|
+
print("Status:", result["status"]) # "successful" | "pending" | "failed"
|
|
37
|
+
|
|
38
|
+
asyncio.run(main())
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
| Method | Description |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `BazikClient(user_id, secret_key, base_url=…, timeout=20)` | Create a client. |
|
|
46
|
+
| `await authenticate()` | Fetch & cache a bearer token (auto-called). |
|
|
47
|
+
| `await create_moncash_payment(*, gdes, reference_id, …)` | Create a MonCash payment → `{orderId, redirectUrl, …}`. |
|
|
48
|
+
| `await verify_payment(order_id)` | Current status → `{status, referenceId, …}`. |
|
|
49
|
+
| `await wait_for_completion(order_id, *, interval=3, timeout=180)` | Poll until resolved. |
|
|
50
|
+
|
|
51
|
+
`create_moncash_payment` optional fields: `webhook_url`, `description`, `customer_first_name`, `customer_last_name`, `customer_email`, `success_url`, `error_url`.
|
|
52
|
+
|
|
53
|
+
Errors raise `bazik.BazikError` (`.status_code`, `.body`).
|
|
54
|
+
|
|
55
|
+
> **Note:** the amount field is `gdes` (gourdes) — not `gourdes`. Sending the wrong key creates a payment with no amount.
|
|
56
|
+
|
|
57
|
+
## Webhook flow
|
|
58
|
+
|
|
59
|
+
Bazik POSTs `{ orderId }` to your `webhook_url` on a status change. **Don't trust the body** — verify:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
verified = await client.verify_payment(order_id)
|
|
63
|
+
if verified["status"] == "successful":
|
|
64
|
+
... # activate the order
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT © Falandy Jean. Unofficial SDK; not affiliated with Bazik.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Async Bazik client for MonCash payments in Haiti.
|
|
2
|
+
|
|
3
|
+
Battle-tested in production (TradeMakaya). Mirrors the official Bazik API over HTTP:
|
|
4
|
+
|
|
5
|
+
POST /token {userID, secretKey} -> { token, expires_at }
|
|
6
|
+
POST /moncash/token (Bearer) {gdes, referenceId…} -> { orderId, redirectUrl, status }
|
|
7
|
+
GET /order/{orderId} (Bearer) -> { status, referenceId, … }
|
|
8
|
+
|
|
9
|
+
The amount field is **gdes** (gourdes), NOT "gourdes" — sending the wrong key makes Bazik
|
|
10
|
+
create a payment without an amount.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import time
|
|
16
|
+
from typing import Any, Optional
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from .errors import BazikError
|
|
21
|
+
|
|
22
|
+
DEFAULT_BASE_URL = "https://api.bazik.io"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BazikClient:
|
|
26
|
+
"""A minimal, dependency-light (httpx only) async client for the Bazik API.
|
|
27
|
+
|
|
28
|
+
Example
|
|
29
|
+
-------
|
|
30
|
+
>>> client = BazikClient(user_id="...", secret_key="...")
|
|
31
|
+
>>> order = await client.create_moncash_payment(gdes=500, reference_id="INV-1",
|
|
32
|
+
... webhook_url="https://api.me/webhook")
|
|
33
|
+
>>> print(order["redirectUrl"]) # send the customer here to pay
|
|
34
|
+
>>> result = await client.wait_for_completion(order["orderId"])
|
|
35
|
+
>>> assert result["status"] == "successful"
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, user_id: str, secret_key: str, base_url: str = DEFAULT_BASE_URL,
|
|
39
|
+
timeout: float = 20.0):
|
|
40
|
+
if not user_id or not secret_key:
|
|
41
|
+
raise ValueError("BazikClient requires user_id and secret_key.")
|
|
42
|
+
self.user_id = user_id
|
|
43
|
+
self.secret_key = secret_key
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self._token: Optional[str] = None
|
|
47
|
+
self._token_exp: float = 0.0
|
|
48
|
+
|
|
49
|
+
# ---- auth -----------------------------------------------------------------
|
|
50
|
+
async def authenticate(self) -> str:
|
|
51
|
+
"""Fetch (and cache) a bearer token. Called automatically by other methods."""
|
|
52
|
+
now = time.time()
|
|
53
|
+
if self._token and now < self._token_exp - 60:
|
|
54
|
+
return self._token
|
|
55
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
56
|
+
r = await client.post(f"{self.base_url}/token",
|
|
57
|
+
json={"userID": self.user_id, "secretKey": self.secret_key})
|
|
58
|
+
if r.status_code >= 400:
|
|
59
|
+
raise BazikError(f"/token failed ({r.status_code})", r.status_code, r.text[:300])
|
|
60
|
+
try:
|
|
61
|
+
data = r.json()
|
|
62
|
+
except Exception:
|
|
63
|
+
raise BazikError("/token: non-JSON response", r.status_code, r.text[:300])
|
|
64
|
+
token = data.get("token") or data.get("access_token") or (data.get("data") or {}).get("token")
|
|
65
|
+
if not token:
|
|
66
|
+
raise BazikError(f"/token: no token in response: {str(data)[:200]}")
|
|
67
|
+
self._token = token
|
|
68
|
+
exp_at = data.get("expires_at")
|
|
69
|
+
self._token_exp = (exp_at / 1000) if isinstance(exp_at, (int, float)) else now + 86400
|
|
70
|
+
return token
|
|
71
|
+
|
|
72
|
+
async def _headers(self) -> dict:
|
|
73
|
+
return {"Authorization": f"Bearer {await self.authenticate()}",
|
|
74
|
+
"Content-Type": "application/json"}
|
|
75
|
+
|
|
76
|
+
# ---- payments -------------------------------------------------------------
|
|
77
|
+
async def create_moncash_payment(self, *, gdes: int, reference_id: str,
|
|
78
|
+
webhook_url: Optional[str] = None,
|
|
79
|
+
description: Optional[str] = None,
|
|
80
|
+
customer_first_name: Optional[str] = None,
|
|
81
|
+
customer_last_name: Optional[str] = None,
|
|
82
|
+
customer_email: Optional[str] = None,
|
|
83
|
+
success_url: Optional[str] = None,
|
|
84
|
+
error_url: Optional[str] = None) -> dict[str, Any]:
|
|
85
|
+
"""Create a MonCash payment (max 75,000 HTG). Returns Bazik's raw response,
|
|
86
|
+
including ``orderId`` and ``redirectUrl`` (where you send the customer to pay)."""
|
|
87
|
+
payload: dict[str, Any] = {"gdes": gdes, "referenceId": reference_id}
|
|
88
|
+
for k, v in (("description", description), ("customerFirstName", customer_first_name),
|
|
89
|
+
("customerLastName", customer_last_name), ("customerEmail", customer_email),
|
|
90
|
+
("webhookUrl", webhook_url), ("successUrl", success_url), ("errorUrl", error_url)):
|
|
91
|
+
if v:
|
|
92
|
+
payload[k] = v
|
|
93
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
94
|
+
r = await client.post(f"{self.base_url}/moncash/token", json=payload,
|
|
95
|
+
headers=await self._headers())
|
|
96
|
+
if r.status_code >= 400:
|
|
97
|
+
raise BazikError(f"/moncash/token failed ({r.status_code})", r.status_code, r.text[:300])
|
|
98
|
+
return r.json()
|
|
99
|
+
|
|
100
|
+
async def verify_payment(self, order_id: str) -> dict[str, Any]:
|
|
101
|
+
"""Fetch a payment's current status. ``status == 'successful'`` once paid
|
|
102
|
+
(other values: ``'pending'``, ``'failed'``)."""
|
|
103
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
104
|
+
r = await client.get(f"{self.base_url}/order/{order_id}", headers=await self._headers())
|
|
105
|
+
if r.status_code >= 400:
|
|
106
|
+
raise BazikError(f"/order failed ({r.status_code})", r.status_code, r.text[:300])
|
|
107
|
+
return r.json()
|
|
108
|
+
|
|
109
|
+
async def wait_for_completion(self, order_id: str, *, interval: float = 3.0,
|
|
110
|
+
timeout: float = 180.0) -> dict[str, Any]:
|
|
111
|
+
"""Poll ``verify_payment`` until the order resolves (successful/failed) or the
|
|
112
|
+
timeout elapses. Returns the last status payload."""
|
|
113
|
+
deadline = time.time() + timeout
|
|
114
|
+
last: dict[str, Any] = {}
|
|
115
|
+
while time.time() < deadline:
|
|
116
|
+
last = await self.verify_payment(order_id)
|
|
117
|
+
if (last.get("status") or "").lower() in ("successful", "failed", "canceled", "cancelled"):
|
|
118
|
+
return last
|
|
119
|
+
await asyncio.sleep(interval)
|
|
120
|
+
return last
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BazikError(RuntimeError):
|
|
5
|
+
"""Raised when the Bazik API returns an error or an unexpected response."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, message: str, status_code: int | None = None, body: str | None = None):
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
self.status_code = status_code
|
|
10
|
+
self.body = body
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bazik-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Async Python SDK for the Bazik API — MonCash payments for Haiti."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Falandy Jean" }]
|
|
13
|
+
keywords = ["bazik", "moncash", "haiti", "payments", "natcash"]
|
|
14
|
+
dependencies = ["httpx>=0.24"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/FalandyJEAN/bazik-sdk-python"
|
|
18
|
+
Issues = "https://github.com/FalandyJEAN/bazik-sdk-python/issues"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["bazik"]
|