zenopay-sdk 0.1.0__py3-none-any.whl → 0.2.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.
- elusion/zenopay/__init__.py +1 -1
- elusion/zenopay/client.py +23 -101
- elusion/zenopay/config.py +24 -21
- elusion/zenopay/http/client.py +162 -48
- elusion/zenopay/models/__init__.py +10 -7
- elusion/zenopay/models/common.py +12 -10
- elusion/zenopay/models/disbursement.py +70 -0
- elusion/zenopay/models/order.py +25 -84
- elusion/zenopay/services/__init__.py +2 -4
- elusion/zenopay/services/base.py +63 -37
- elusion/zenopay/services/disbursements.py +45 -0
- elusion/zenopay/services/orders.py +44 -40
- elusion/zenopay/utils/__init__.py +2 -8
- elusion/zenopay/utils/helpers.py +56 -0
- {zenopay_sdk-0.1.0.dist-info → zenopay_sdk-0.2.0.dist-info}/METADATA +195 -90
- zenopay_sdk-0.2.0.dist-info/RECORD +25 -0
- zenopay_sdk-0.1.0.dist-info/RECORD +0 -23
- {zenopay_sdk-0.1.0.dist-info → zenopay_sdk-0.2.0.dist-info}/WHEEL +0 -0
- {zenopay_sdk-0.1.0.dist-info → zenopay_sdk-0.2.0.dist-info}/licenses/LICENSE +0 -0
elusion/zenopay/__init__.py
CHANGED
elusion/zenopay/client.py
CHANGED
@@ -8,6 +8,7 @@ from elusion.zenopay.config import ZenoPayConfig
|
|
8
8
|
from elusion.zenopay.http import HTTPClient
|
9
9
|
from elusion.zenopay.services import OrderService
|
10
10
|
from elusion.zenopay.services import WebhookService
|
11
|
+
from elusion.zenopay.services import DisbursementService
|
11
12
|
|
12
13
|
logger = logging.getLogger(__name__)
|
13
14
|
|
@@ -20,33 +21,31 @@ class ZenoPayClient:
|
|
20
21
|
|
21
22
|
Examples:
|
22
23
|
Basic usage:
|
23
|
-
>>> client = ZenoPayClient(
|
24
|
+
>>> client = ZenoPayClient(api_key="your-api-key")
|
24
25
|
|
25
26
|
Async usage:
|
26
|
-
>>> async with ZenoPayClient(
|
27
|
+
>>> async with ZenoPayClient(api_key="your-api-key") as client:
|
27
28
|
... order = await client.orders.create({
|
28
|
-
... "buyer_email": "
|
29
|
-
... "buyer_name": "
|
30
|
-
... "buyer_phone": "
|
29
|
+
... "buyer_email": "example@example.xyz",
|
30
|
+
... "buyer_name": "example name",
|
31
|
+
... "buyer_phone": "06XXXXXXXX",
|
31
32
|
... "amount": 1000,
|
32
|
-
... "webhook_url": "https://yourwebsite.
|
33
|
+
... "webhook_url": "https://yourwebsite.xyz/webhook"
|
33
34
|
... })
|
34
35
|
|
35
36
|
Sync usage:
|
36
|
-
>>> with ZenoPayClient(
|
37
|
-
... order = client.orders.
|
38
|
-
... "buyer_email": "
|
39
|
-
... "buyer_name": "
|
40
|
-
... "buyer_phone": "
|
37
|
+
>>> with ZenoPayClient(api_key="your-api-key") as client:
|
38
|
+
... order = client.orders.sync.create({
|
39
|
+
... "buyer_email": "example@example.xyz",
|
40
|
+
... "buyer_name": "example name",
|
41
|
+
... "buyer_phone": "06XXXXXXXX",
|
41
42
|
... "amount": 1000
|
42
43
|
... })
|
43
44
|
"""
|
44
45
|
|
45
46
|
def __init__(
|
46
47
|
self,
|
47
|
-
account_id: str,
|
48
48
|
api_key: Optional[str] = None,
|
49
|
-
secret_key: Optional[str] = None,
|
50
49
|
base_url: Optional[str] = None,
|
51
50
|
timeout: Optional[float] = None,
|
52
51
|
max_retries: Optional[int] = None,
|
@@ -54,30 +53,13 @@ class ZenoPayClient:
|
|
54
53
|
"""Initialize the ZenoPay client.
|
55
54
|
|
56
55
|
Args:
|
57
|
-
account_id: Your ZenoPay account ID (required).
|
58
56
|
api_key: API key (optional, can be set via environment variable).
|
59
|
-
secret_key: Secret key (optional, can be set via environment variable).
|
60
57
|
base_url: Base URL for the API (optional, defaults to production).
|
61
58
|
timeout: Request timeout in seconds (optional).
|
62
59
|
max_retries: Maximum number of retries for failed requests (optional).
|
63
|
-
**kwargs: Additional configuration options.
|
64
|
-
|
65
|
-
Examples:
|
66
|
-
>>> # Using environment variables for API keys
|
67
|
-
>>> client = ZenoPayClient(account_id="zp87778")
|
68
|
-
|
69
|
-
>>> # Explicit configuration
|
70
|
-
>>> client = ZenoPayClient(
|
71
|
-
... account_id="zp87778",
|
72
|
-
... api_key="your_api_key",
|
73
|
-
... secret_key="your_secret_key",
|
74
|
-
... timeout=30.0
|
75
|
-
... )
|
76
60
|
"""
|
77
61
|
self.config = ZenoPayConfig(
|
78
|
-
account_id=account_id,
|
79
62
|
api_key=api_key,
|
80
|
-
secret_key=secret_key,
|
81
63
|
base_url=base_url,
|
82
64
|
timeout=timeout,
|
83
65
|
max_retries=max_retries,
|
@@ -87,12 +69,13 @@ class ZenoPayClient:
|
|
87
69
|
|
88
70
|
# Initialize services
|
89
71
|
self.orders = OrderService(self.http_client, self.config)
|
72
|
+
self.disbursements = DisbursementService(self.http_client, self.config)
|
90
73
|
self.webhooks = WebhookService()
|
91
74
|
|
92
|
-
logger.info(f"ZenoPay client initialized for account: {
|
75
|
+
logger.info(f"ZenoPay client initialized for account: {api_key}")
|
93
76
|
|
94
77
|
async def __aenter__(self) -> "ZenoPayClient":
|
95
|
-
"""
|
78
|
+
"""Enter async context manager."""
|
96
79
|
await self.http_client.__aenter__()
|
97
80
|
return self
|
98
81
|
|
@@ -102,12 +85,11 @@ class ZenoPayClient:
|
|
102
85
|
exc_val: Optional[BaseException],
|
103
86
|
exc_tb: Optional[TracebackType],
|
104
87
|
) -> None:
|
105
|
-
"""
|
106
|
-
await self.http_client.__aexit__(exc_type, exc_val, exc_tb)
|
88
|
+
"""Exit async context manager."""
|
107
89
|
await self.http_client.__aexit__(exc_type, exc_val, exc_tb)
|
108
90
|
|
109
91
|
def __enter__(self) -> "ZenoPayClient":
|
110
|
-
"""
|
92
|
+
"""Enter sync context manager."""
|
111
93
|
self.http_client.__enter__()
|
112
94
|
return self
|
113
95
|
|
@@ -117,7 +99,7 @@ class ZenoPayClient:
|
|
117
99
|
exc_val: Optional[BaseException],
|
118
100
|
exc_tb: Optional[TracebackType],
|
119
101
|
) -> None:
|
120
|
-
"""
|
102
|
+
"""Exit sync context manager."""
|
121
103
|
self.http_client.__exit__(exc_type, exc_val, exc_tb)
|
122
104
|
|
123
105
|
async def close(self) -> None:
|
@@ -128,50 +110,10 @@ class ZenoPayClient:
|
|
128
110
|
"""Close the client and cleanup resources (sync version)."""
|
129
111
|
self.http_client.close_sync()
|
130
112
|
|
131
|
-
def test_connection(self) -> bool:
|
132
|
-
"""Test the connection to ZenoPay API.
|
133
|
-
|
134
|
-
Returns:
|
135
|
-
True if connection is successful, False otherwise.
|
136
|
-
|
137
|
-
Examples:
|
138
|
-
>>> client = ZenoPayClient(account_id="zp87778")
|
139
|
-
>>> if client.test_connection():
|
140
|
-
... print("Connection successful!")
|
141
|
-
... else:
|
142
|
-
... print("Connection failed!")
|
143
|
-
"""
|
144
|
-
try:
|
145
|
-
self.orders.get_status_sync("test-connection-check")
|
146
|
-
return True
|
147
|
-
except Exception as e:
|
148
|
-
logger.debug(f"Connection test failed: {e}")
|
149
|
-
return False
|
150
|
-
|
151
|
-
async def test_connection_async(self) -> bool:
|
152
|
-
"""Test the connection to ZenoPay API (async version).
|
153
|
-
|
154
|
-
Returns:
|
155
|
-
True if connection is successful, False otherwise.
|
156
|
-
|
157
|
-
Examples:
|
158
|
-
>>> async with ZenoPayClient(account_id="zp87778") as client:
|
159
|
-
... if await client.test_connection_async():
|
160
|
-
... print("Connection successful!")
|
161
|
-
... else:
|
162
|
-
... print("Connection failed!")
|
163
|
-
"""
|
164
|
-
try:
|
165
|
-
await self.orders.get_status("test-connection-check")
|
166
|
-
return True
|
167
|
-
except Exception as e:
|
168
|
-
logger.debug(f"Connection test failed: {e}")
|
169
|
-
return False
|
170
|
-
|
171
113
|
@property
|
172
|
-
def
|
173
|
-
"""Get the
|
174
|
-
return self.config.
|
114
|
+
def api_key(self) -> str:
|
115
|
+
"""Get the current API key."""
|
116
|
+
return self.config.api_key or ""
|
175
117
|
|
176
118
|
@property
|
177
119
|
def base_url(self) -> str:
|
@@ -179,29 +121,9 @@ class ZenoPayClient:
|
|
179
121
|
return self.config.base_url
|
180
122
|
|
181
123
|
def get_config(self) -> ZenoPayConfig:
|
182
|
-
"""Get the current configuration.
|
183
|
-
|
184
|
-
Returns:
|
185
|
-
Current ZenoPayConfig instance.
|
186
|
-
"""
|
124
|
+
"""Get the current configuration."""
|
187
125
|
return self.config
|
188
126
|
|
189
|
-
def update_config(self, **kwargs: object) -> None:
|
190
|
-
"""Update client configuration.
|
191
|
-
|
192
|
-
Args:
|
193
|
-
**kwargs: Configuration parameters to update.
|
194
|
-
|
195
|
-
Examples:
|
196
|
-
>>> client = ZenoPayClient(account_id="zp87778")
|
197
|
-
>>> client.update_config(timeout=60.0, max_retries=5)
|
198
|
-
"""
|
199
|
-
for key, value in kwargs.items():
|
200
|
-
if hasattr(self.config, key):
|
201
|
-
setattr(self.config, key, value)
|
202
|
-
else:
|
203
|
-
logger.warning(f"Unknown configuration parameter: {key}")
|
204
|
-
|
205
127
|
def __repr__(self) -> str:
|
206
128
|
"""String representation of the client."""
|
207
|
-
return f"ZenoPayClient(
|
129
|
+
return f"ZenoPayClient(api_key='{self.api_key}', base_url='{self.base_url}')"
|
elusion/zenopay/config.py
CHANGED
@@ -4,19 +4,15 @@ import os
|
|
4
4
|
from typing import Dict, Optional
|
5
5
|
from dotenv import load_dotenv
|
6
6
|
|
7
|
-
# Load environment variables from .env file if it exists
|
8
7
|
load_dotenv()
|
9
8
|
|
10
|
-
|
11
|
-
DEFAULT_BASE_URL = "https://api.zeno.africa"
|
9
|
+
DEFAULT_BASE_URL = "https://zenoapi.com"
|
12
10
|
DEFAULT_TIMEOUT = 30.0
|
13
11
|
DEFAULT_MAX_RETRIES = 3
|
14
12
|
DEFAULT_RETRY_DELAY = 1.0
|
15
13
|
|
16
14
|
# Environment variable names
|
17
15
|
ENV_API_KEY = "ZENOPAY_API_KEY"
|
18
|
-
ENV_SECRET_KEY = "ZENOPAY_SECRET_KEY"
|
19
|
-
ENV_ACCOUNT_ID = "ZENOPAY_ACCOUNT_ID"
|
20
16
|
ENV_BASE_URL = "ZENOPAY_BASE_URL"
|
21
17
|
ENV_TIMEOUT = "ZENOPAY_TIMEOUT"
|
22
18
|
|
@@ -24,13 +20,14 @@ ENV_TIMEOUT = "ZENOPAY_TIMEOUT"
|
|
24
20
|
DEFAULT_HEADERS = {
|
25
21
|
"User-Agent": "zenopay-python-sdk",
|
26
22
|
"Accept": "application/json",
|
27
|
-
"Content-Type": "application/
|
23
|
+
"Content-Type": "application/json",
|
28
24
|
}
|
29
25
|
|
30
26
|
# API endpoints
|
31
27
|
ENDPOINTS = {
|
32
|
-
"create_order": "",
|
33
|
-
"order_status": "/order-status",
|
28
|
+
"create_order": "/api/payments/mobile_money_tanzania",
|
29
|
+
"order_status": "/api/payments/order-status",
|
30
|
+
"disbursement": "/api/payments/walletcashin/process/",
|
34
31
|
}
|
35
32
|
|
36
33
|
# Payment statuses
|
@@ -48,8 +45,6 @@ class ZenoPayConfig:
|
|
48
45
|
def __init__(
|
49
46
|
self,
|
50
47
|
api_key: Optional[str] = None,
|
51
|
-
secret_key: Optional[str] = None,
|
52
|
-
account_id: Optional[str] = None,
|
53
48
|
base_url: Optional[str] = None,
|
54
49
|
timeout: Optional[float] = None,
|
55
50
|
max_retries: Optional[int] = None,
|
@@ -60,22 +55,18 @@ class ZenoPayConfig:
|
|
60
55
|
|
61
56
|
Args:
|
62
57
|
api_key: ZenoPay API key. If not provided, will try to get from environment.
|
63
|
-
secret_key: ZenoPay secret key. If not provided, will try to get from environment.
|
64
|
-
account_id: ZenoPay account ID. If not provided, will try to get from environment.
|
65
58
|
base_url: Base URL for the ZenoPay API.
|
66
59
|
timeout: Request timeout in seconds.
|
67
60
|
max_retries: Maximum number of retries for failed requests.
|
68
61
|
retry_delay: Delay between retries in seconds.
|
69
62
|
headers: Additional headers to include in requests.
|
70
63
|
"""
|
71
|
-
self.api_key =
|
72
|
-
self.secret_key = secret_key or os.getenv(ENV_SECRET_KEY)
|
73
|
-
self.account_id = account_id or os.getenv(ENV_ACCOUNT_ID)
|
64
|
+
self.api_key = os.getenv(ENV_API_KEY) or api_key
|
74
65
|
|
75
|
-
if not self.
|
76
|
-
raise ValueError(f"
|
66
|
+
if not self.api_key:
|
67
|
+
raise ValueError(f"API key is required. Set {ENV_API_KEY} environment variable " "or pass api_key parameter.")
|
77
68
|
|
78
|
-
self.base_url =
|
69
|
+
self.base_url = os.getenv(ENV_BASE_URL) or base_url or DEFAULT_BASE_URL
|
79
70
|
|
80
71
|
# Parse timeout from environment if provided
|
81
72
|
env_timeout = os.getenv(ENV_TIMEOUT)
|
@@ -91,8 +82,12 @@ class ZenoPayConfig:
|
|
91
82
|
self.max_retries = max_retries or DEFAULT_MAX_RETRIES
|
92
83
|
self.retry_delay = retry_delay or DEFAULT_RETRY_DELAY
|
93
84
|
|
94
|
-
# Merge default headers with custom headers
|
95
85
|
self.headers = DEFAULT_HEADERS.copy()
|
86
|
+
|
87
|
+
if self.api_key:
|
88
|
+
self.headers["x-api-key"] = self.api_key
|
89
|
+
|
90
|
+
# Add any additional custom headers
|
96
91
|
if headers:
|
97
92
|
self.headers.update(headers)
|
98
93
|
|
@@ -104,13 +99,21 @@ class ZenoPayConfig:
|
|
104
99
|
|
105
100
|
Returns:
|
106
101
|
Full URL for the endpoint.
|
102
|
+
|
103
|
+
Raises:
|
104
|
+
ValueError: If endpoint is not found in ENDPOINTS.
|
107
105
|
"""
|
108
106
|
if endpoint not in ENDPOINTS:
|
109
|
-
raise ValueError(f"Unknown endpoint: {endpoint}")
|
107
|
+
raise ValueError(f"Unknown endpoint: {endpoint}. Available endpoints: {list(ENDPOINTS.keys())}")
|
110
108
|
|
111
109
|
endpoint_path = ENDPOINTS[endpoint]
|
112
110
|
if endpoint_path:
|
113
111
|
return f"{self.base_url.rstrip('/')}{endpoint_path}"
|
114
112
|
else:
|
115
|
-
# For create_order, the base URL is the endpoint
|
116
113
|
return self.base_url
|
114
|
+
|
115
|
+
def __repr__(self) -> str:
|
116
|
+
"""String representation of the config."""
|
117
|
+
# Mask API key for security
|
118
|
+
masked_api_key = f"{self.api_key[:8]}..." if self.api_key and len(self.api_key) > 8 else self.api_key
|
119
|
+
return f"ZenoPayConfig(api_key='{masked_api_key}', base_url='{self.base_url}')"
|