zenopay-sdk 0.0.1__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/__init__.py +3 -0
- elusion/zenopay/__init__.py +42 -0
- elusion/zenopay/client.py +207 -0
- elusion/zenopay/config.py +116 -0
- elusion/zenopay/exceptions.py +227 -0
- elusion/zenopay/http/__init__.py +5 -0
- elusion/zenopay/http/client.py +320 -0
- elusion/zenopay/models/__init__.py +43 -0
- elusion/zenopay/models/common.py +93 -0
- elusion/zenopay/models/order.py +251 -0
- elusion/zenopay/models/payment.py +304 -0
- elusion/zenopay/models/webhook.py +122 -0
- elusion/zenopay/services/__init__.py +7 -0
- elusion/zenopay/services/base.py +175 -0
- elusion/zenopay/services/orders.py +135 -0
- elusion/zenopay/services/webhooks.py +188 -0
- elusion/zenopay/utils/__init__.py +9 -0
- elusion/zenopay/utils/helpers.py +35 -0
- zenopay_sdk-0.0.1.dist-info/METADATA +387 -0
- zenopay_sdk-0.0.1.dist-info/RECORD +22 -0
- zenopay_sdk-0.0.1.dist-info/WHEEL +4 -0
- zenopay_sdk-0.0.1.dist-info/licenses/LICENSE +21 -0
elusion/__init__.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
"""ZenoPay SDK for Python.
|
2
|
+
|
3
|
+
A modern Python SDK for the ZenoPay payment API with support for USSD payments,
|
4
|
+
order management, and webhook handling.
|
5
|
+
"""
|
6
|
+
|
7
|
+
__version__ = "0.0.1"
|
8
|
+
__author__ = "Elution Hub"
|
9
|
+
__email__ = "elusion.lab@gmail.com"
|
10
|
+
|
11
|
+
from elusion.zenopay.client import ZenoPayClient as ZenoPay
|
12
|
+
from elusion.zenopay.exceptions import (
|
13
|
+
ZenoPayError,
|
14
|
+
ZenoPayAPIError,
|
15
|
+
ZenoPayAuthenticationError,
|
16
|
+
ZenoPayValidationError,
|
17
|
+
ZenoPayNetworkError,
|
18
|
+
)
|
19
|
+
from elusion.zenopay.models import (
|
20
|
+
Order,
|
21
|
+
NewOrder,
|
22
|
+
OrderStatus,
|
23
|
+
WebhookEvent,
|
24
|
+
WebhookPayload,
|
25
|
+
)
|
26
|
+
|
27
|
+
__all__ = [
|
28
|
+
# Main client
|
29
|
+
"ZenoPay",
|
30
|
+
# Exceptions
|
31
|
+
"ZenoPayError",
|
32
|
+
"ZenoPayAPIError",
|
33
|
+
"ZenoPayAuthenticationError",
|
34
|
+
"ZenoPayValidationError",
|
35
|
+
"ZenoPayNetworkError",
|
36
|
+
# Models
|
37
|
+
"Order",
|
38
|
+
"NewOrder",
|
39
|
+
"OrderStatus",
|
40
|
+
"WebhookEvent",
|
41
|
+
"WebhookPayload",
|
42
|
+
]
|
@@ -0,0 +1,207 @@
|
|
1
|
+
"""Main client for the ZenoPay SDK."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from typing import Optional, Type
|
5
|
+
from types import TracebackType
|
6
|
+
|
7
|
+
from elusion.zenopay.config import ZenoPayConfig
|
8
|
+
from elusion.zenopay.http import HTTPClient
|
9
|
+
from elusion.zenopay.services import OrderService
|
10
|
+
from elusion.zenopay.services import WebhookService
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class ZenoPayClient:
|
16
|
+
"""Main client for interacting with the ZenoPay API.
|
17
|
+
|
18
|
+
This client provides access to all ZenoPay services including order management,
|
19
|
+
payment processing, and webhook handling. It supports both async and sync operations.
|
20
|
+
|
21
|
+
Examples:
|
22
|
+
Basic usage:
|
23
|
+
>>> client = ZenoPayClient(account_id="zp87778")
|
24
|
+
|
25
|
+
Async usage:
|
26
|
+
>>> async with ZenoPayClient(account_id="zp87778") as client:
|
27
|
+
... order = await client.orders.create({
|
28
|
+
... "buyer_email": "jackson@gmail.com",
|
29
|
+
... "buyer_name": "Jackson Dastani",
|
30
|
+
... "buyer_phone": "0652449389",
|
31
|
+
... "amount": 1000,
|
32
|
+
... "webhook_url": "https://yourwebsite.com/webhook"
|
33
|
+
... })
|
34
|
+
|
35
|
+
Sync usage:
|
36
|
+
>>> with ZenoPayClient(account_id="zp87778") as client:
|
37
|
+
... order = client.orders.create_sync({
|
38
|
+
... "buyer_email": "jackson@gmail.com",
|
39
|
+
... "buyer_name": "Jackson Dastani",
|
40
|
+
... "buyer_phone": "0652449389",
|
41
|
+
... "amount": 1000
|
42
|
+
... })
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
account_id: str,
|
48
|
+
api_key: Optional[str] = None,
|
49
|
+
secret_key: Optional[str] = None,
|
50
|
+
base_url: Optional[str] = None,
|
51
|
+
timeout: Optional[float] = None,
|
52
|
+
max_retries: Optional[int] = None,
|
53
|
+
):
|
54
|
+
"""Initialize the ZenoPay client.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
account_id: Your ZenoPay account ID (required).
|
58
|
+
api_key: API key (optional, can be set via environment variable).
|
59
|
+
secret_key: Secret key (optional, can be set via environment variable).
|
60
|
+
base_url: Base URL for the API (optional, defaults to production).
|
61
|
+
timeout: Request timeout in seconds (optional).
|
62
|
+
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
|
+
"""
|
77
|
+
self.config = ZenoPayConfig(
|
78
|
+
account_id=account_id,
|
79
|
+
api_key=api_key,
|
80
|
+
secret_key=secret_key,
|
81
|
+
base_url=base_url,
|
82
|
+
timeout=timeout,
|
83
|
+
max_retries=max_retries,
|
84
|
+
)
|
85
|
+
|
86
|
+
self.http_client = HTTPClient(self.config)
|
87
|
+
|
88
|
+
# Initialize services
|
89
|
+
self.orders = OrderService(self.http_client, self.config)
|
90
|
+
self.webhooks = WebhookService()
|
91
|
+
|
92
|
+
logger.info(f"ZenoPay client initialized for account: {account_id}")
|
93
|
+
|
94
|
+
async def __aenter__(self) -> "ZenoPayClient":
|
95
|
+
"""Async context manager entry."""
|
96
|
+
await self.http_client.__aenter__()
|
97
|
+
return self
|
98
|
+
|
99
|
+
async def __aexit__(
|
100
|
+
self,
|
101
|
+
exc_type: Optional[Type[BaseException]],
|
102
|
+
exc_val: Optional[BaseException],
|
103
|
+
exc_tb: Optional[TracebackType],
|
104
|
+
) -> None:
|
105
|
+
"""Async context manager exit."""
|
106
|
+
await self.http_client.__aexit__(exc_type, exc_val, exc_tb)
|
107
|
+
await self.http_client.__aexit__(exc_type, exc_val, exc_tb)
|
108
|
+
|
109
|
+
def __enter__(self) -> "ZenoPayClient":
|
110
|
+
"""Sync context manager entry."""
|
111
|
+
self.http_client.__enter__()
|
112
|
+
return self
|
113
|
+
|
114
|
+
def __exit__(
|
115
|
+
self,
|
116
|
+
exc_type: Optional[Type[BaseException]],
|
117
|
+
exc_val: Optional[BaseException],
|
118
|
+
exc_tb: Optional[TracebackType],
|
119
|
+
) -> None:
|
120
|
+
"""Sync context manager exit."""
|
121
|
+
self.http_client.__exit__(exc_type, exc_val, exc_tb)
|
122
|
+
|
123
|
+
async def close(self) -> None:
|
124
|
+
"""Close the client and cleanup resources."""
|
125
|
+
await self.http_client.close()
|
126
|
+
|
127
|
+
def close_sync(self) -> None:
|
128
|
+
"""Close the client and cleanup resources (sync version)."""
|
129
|
+
self.http_client.close_sync()
|
130
|
+
|
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
|
+
@property
|
172
|
+
def account_id(self) -> str:
|
173
|
+
"""Get the account ID."""
|
174
|
+
return self.config.account_id or ""
|
175
|
+
|
176
|
+
@property
|
177
|
+
def base_url(self) -> str:
|
178
|
+
"""Get the base URL."""
|
179
|
+
return self.config.base_url
|
180
|
+
|
181
|
+
def get_config(self) -> ZenoPayConfig:
|
182
|
+
"""Get the current configuration.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
Current ZenoPayConfig instance.
|
186
|
+
"""
|
187
|
+
return self.config
|
188
|
+
|
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
|
+
def __repr__(self) -> str:
|
206
|
+
"""String representation of the client."""
|
207
|
+
return f"ZenoPayClient(account_id='{self.account_id}', base_url='{self.base_url}')"
|
@@ -0,0 +1,116 @@
|
|
1
|
+
"""Configuration and constants for the ZenoPay SDK."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from typing import Dict, Optional
|
5
|
+
from dotenv import load_dotenv
|
6
|
+
|
7
|
+
# Load environment variables from .env file if it exists
|
8
|
+
load_dotenv()
|
9
|
+
|
10
|
+
# Default configuration
|
11
|
+
DEFAULT_BASE_URL = "https://api.zeno.africa"
|
12
|
+
DEFAULT_TIMEOUT = 30.0
|
13
|
+
DEFAULT_MAX_RETRIES = 3
|
14
|
+
DEFAULT_RETRY_DELAY = 1.0
|
15
|
+
|
16
|
+
# Environment variable names
|
17
|
+
ENV_API_KEY = "ZENOPAY_API_KEY"
|
18
|
+
ENV_SECRET_KEY = "ZENOPAY_SECRET_KEY"
|
19
|
+
ENV_ACCOUNT_ID = "ZENOPAY_ACCOUNT_ID"
|
20
|
+
ENV_BASE_URL = "ZENOPAY_BASE_URL"
|
21
|
+
ENV_TIMEOUT = "ZENOPAY_TIMEOUT"
|
22
|
+
|
23
|
+
# HTTP headers
|
24
|
+
DEFAULT_HEADERS = {
|
25
|
+
"User-Agent": "zenopay-python-sdk",
|
26
|
+
"Accept": "application/json",
|
27
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
28
|
+
}
|
29
|
+
|
30
|
+
# API endpoints
|
31
|
+
ENDPOINTS = {
|
32
|
+
"create_order": "",
|
33
|
+
"order_status": "/order-status",
|
34
|
+
}
|
35
|
+
|
36
|
+
# Payment statuses
|
37
|
+
PAYMENT_STATUSES = {
|
38
|
+
"PENDING": "PENDING",
|
39
|
+
"COMPLETED": "COMPLETED",
|
40
|
+
"FAILED": "FAILED",
|
41
|
+
"CANCELLED": "CANCELLED",
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
class ZenoPayConfig:
|
46
|
+
"""Configuration class for the ZenoPay SDK."""
|
47
|
+
|
48
|
+
def __init__(
|
49
|
+
self,
|
50
|
+
api_key: Optional[str] = None,
|
51
|
+
secret_key: Optional[str] = None,
|
52
|
+
account_id: Optional[str] = None,
|
53
|
+
base_url: Optional[str] = None,
|
54
|
+
timeout: Optional[float] = None,
|
55
|
+
max_retries: Optional[int] = None,
|
56
|
+
retry_delay: Optional[float] = None,
|
57
|
+
headers: Optional[Dict[str, str]] = None,
|
58
|
+
) -> None:
|
59
|
+
"""Initialize configuration.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
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
|
+
base_url: Base URL for the ZenoPay API.
|
66
|
+
timeout: Request timeout in seconds.
|
67
|
+
max_retries: Maximum number of retries for failed requests.
|
68
|
+
retry_delay: Delay between retries in seconds.
|
69
|
+
headers: Additional headers to include in requests.
|
70
|
+
"""
|
71
|
+
self.api_key = api_key or os.getenv(ENV_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)
|
74
|
+
|
75
|
+
if not self.account_id:
|
76
|
+
raise ValueError(f"Account ID is required. Set {ENV_ACCOUNT_ID} environment variable " "or pass account_id parameter.")
|
77
|
+
|
78
|
+
self.base_url = base_url or os.getenv(ENV_BASE_URL, DEFAULT_BASE_URL)
|
79
|
+
|
80
|
+
# Parse timeout from environment if provided
|
81
|
+
env_timeout = os.getenv(ENV_TIMEOUT)
|
82
|
+
if env_timeout:
|
83
|
+
try:
|
84
|
+
env_timeout_float = float(env_timeout)
|
85
|
+
except ValueError:
|
86
|
+
env_timeout_float = DEFAULT_TIMEOUT
|
87
|
+
else:
|
88
|
+
env_timeout_float = DEFAULT_TIMEOUT
|
89
|
+
|
90
|
+
self.timeout = timeout or env_timeout_float
|
91
|
+
self.max_retries = max_retries or DEFAULT_MAX_RETRIES
|
92
|
+
self.retry_delay = retry_delay or DEFAULT_RETRY_DELAY
|
93
|
+
|
94
|
+
# Merge default headers with custom headers
|
95
|
+
self.headers = DEFAULT_HEADERS.copy()
|
96
|
+
if headers:
|
97
|
+
self.headers.update(headers)
|
98
|
+
|
99
|
+
def get_endpoint_url(self, endpoint: str) -> str:
|
100
|
+
"""Get the full URL for an endpoint.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
endpoint: Endpoint key from ENDPOINTS dict.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
Full URL for the endpoint.
|
107
|
+
"""
|
108
|
+
if endpoint not in ENDPOINTS:
|
109
|
+
raise ValueError(f"Unknown endpoint: {endpoint}")
|
110
|
+
|
111
|
+
endpoint_path = ENDPOINTS[endpoint]
|
112
|
+
if endpoint_path:
|
113
|
+
return f"{self.base_url.rstrip('/')}{endpoint_path}"
|
114
|
+
else:
|
115
|
+
# For create_order, the base URL is the endpoint
|
116
|
+
return self.base_url
|
@@ -0,0 +1,227 @@
|
|
1
|
+
"""Custom exceptions for the ZenoPay SDK."""
|
2
|
+
|
3
|
+
from typing import Any, Dict, Optional
|
4
|
+
|
5
|
+
|
6
|
+
class ZenoPayError(Exception):
|
7
|
+
"""Base exception for all ZenoPay SDK errors."""
|
8
|
+
|
9
|
+
def __init__(
|
10
|
+
self,
|
11
|
+
message: str,
|
12
|
+
status_code: Optional[int] = None,
|
13
|
+
response_data: Optional[Dict[str, Any]] = None,
|
14
|
+
) -> None:
|
15
|
+
"""Initialize ZenoPayError.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
message: Error message.
|
19
|
+
status_code: HTTP status code if applicable.
|
20
|
+
response_data: Response data from the API if available.
|
21
|
+
"""
|
22
|
+
super().__init__(message)
|
23
|
+
self.message = message
|
24
|
+
self.status_code = status_code
|
25
|
+
self.response_data = response_data or {}
|
26
|
+
|
27
|
+
|
28
|
+
class ZenoPayAPIError(ZenoPayError):
|
29
|
+
"""Exception raised for API errors from the ZenoPay service."""
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
message: str,
|
34
|
+
status_code: int,
|
35
|
+
response_data: Optional[Dict[str, Any]] = None,
|
36
|
+
error_code: Optional[str] = None,
|
37
|
+
) -> None:
|
38
|
+
"""Initialize ZenoPayAPIError.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
message: Error message from the API.
|
42
|
+
status_code: HTTP status code.
|
43
|
+
response_data: Full response data from the API.
|
44
|
+
error_code: Specific error code from the API.
|
45
|
+
"""
|
46
|
+
super().__init__(message, status_code, response_data)
|
47
|
+
self.error_code = error_code
|
48
|
+
|
49
|
+
|
50
|
+
class ZenoPayAuthenticationError(ZenoPayAPIError):
|
51
|
+
"""Exception raised for authentication errors (401)."""
|
52
|
+
|
53
|
+
def __init__(
|
54
|
+
self,
|
55
|
+
message: str = "Invalid API key or secret key",
|
56
|
+
response_data: Optional[Dict[str, Any]] = None,
|
57
|
+
) -> None:
|
58
|
+
super().__init__(message, 401, response_data, "AUTHENTICATION_ERROR")
|
59
|
+
|
60
|
+
|
61
|
+
class ZenoPayAuthorizationError(ZenoPayAPIError):
|
62
|
+
"""Exception raised for authorization errors (403)."""
|
63
|
+
|
64
|
+
def __init__(
|
65
|
+
self,
|
66
|
+
message: str = "Insufficient permissions for this operation",
|
67
|
+
response_data: Optional[Dict[str, Any]] = None,
|
68
|
+
) -> None:
|
69
|
+
super().__init__(message, 403, response_data, "AUTHORIZATION_ERROR")
|
70
|
+
|
71
|
+
|
72
|
+
class ZenoPayNotFoundError(ZenoPayAPIError):
|
73
|
+
"""Exception raised when a resource is not found (404)."""
|
74
|
+
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
message: str = "The requested resource was not found",
|
78
|
+
response_data: Optional[Dict[str, Any]] = None,
|
79
|
+
) -> None:
|
80
|
+
super().__init__(message, 404, response_data, "NOT_FOUND_ERROR")
|
81
|
+
|
82
|
+
|
83
|
+
class ZenoPayValidationError(ZenoPayAPIError):
|
84
|
+
"""Exception raised for validation errors (400, 422)."""
|
85
|
+
|
86
|
+
def __init__(
|
87
|
+
self,
|
88
|
+
message: str,
|
89
|
+
status_code: int = 400,
|
90
|
+
response_data: Optional[Dict[str, Any]] = None,
|
91
|
+
validation_errors: Optional[Dict[str, Any]] = None,
|
92
|
+
) -> None:
|
93
|
+
"""Initialize ZenoPayValidationError.
|
94
|
+
|
95
|
+
Args:
|
96
|
+
message: Error message.
|
97
|
+
status_code: HTTP status code (400 or 422).
|
98
|
+
response_data: Response data from the API.
|
99
|
+
validation_errors: Detailed validation errors by field.
|
100
|
+
"""
|
101
|
+
super().__init__(message, status_code, response_data, "VALIDATION_ERROR")
|
102
|
+
self.validation_errors = validation_errors or {}
|
103
|
+
|
104
|
+
|
105
|
+
class ZenoPayRateLimitError(ZenoPayAPIError):
|
106
|
+
"""Exception raised when rate limit is exceeded (429)."""
|
107
|
+
|
108
|
+
def __init__(
|
109
|
+
self,
|
110
|
+
message: str = "Rate limit exceeded",
|
111
|
+
response_data: Optional[Dict[str, Any]] = None,
|
112
|
+
retry_after: Optional[int] = None,
|
113
|
+
) -> None:
|
114
|
+
"""Initialize ZenoPayRateLimitError.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
message: Error message.
|
118
|
+
response_data: Response data from the API.
|
119
|
+
retry_after: Number of seconds to wait before retrying.
|
120
|
+
"""
|
121
|
+
super().__init__(message, 429, response_data, "RATE_LIMIT_ERROR")
|
122
|
+
self.retry_after = retry_after
|
123
|
+
|
124
|
+
|
125
|
+
class ZenoPayServerError(ZenoPayAPIError):
|
126
|
+
"""Exception raised for server errors (5xx)."""
|
127
|
+
|
128
|
+
def __init__(
|
129
|
+
self,
|
130
|
+
message: str = "Internal server error",
|
131
|
+
status_code: int = 500,
|
132
|
+
response_data: Optional[Dict[str, Any]] = None,
|
133
|
+
) -> None:
|
134
|
+
super().__init__(message, status_code, response_data, "SERVER_ERROR")
|
135
|
+
|
136
|
+
|
137
|
+
class ZenoPayNetworkError(ZenoPayError):
|
138
|
+
"""Exception raised for network-related errors."""
|
139
|
+
|
140
|
+
def __init__(
|
141
|
+
self,
|
142
|
+
message: str = "Network error occurred",
|
143
|
+
original_error: Optional[Exception] = None,
|
144
|
+
) -> None:
|
145
|
+
"""Initialize ZenoPayNetworkError.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
message: Error message.
|
149
|
+
original_error: The original exception that caused this error.
|
150
|
+
"""
|
151
|
+
super().__init__(message)
|
152
|
+
self.original_error = original_error
|
153
|
+
|
154
|
+
|
155
|
+
class ZenoPayTimeoutError(ZenoPayNetworkError):
|
156
|
+
"""Exception raised when a request times out."""
|
157
|
+
|
158
|
+
def __init__(
|
159
|
+
self,
|
160
|
+
message: str = "Request timeout",
|
161
|
+
timeout_duration: Optional[float] = None,
|
162
|
+
) -> None:
|
163
|
+
"""Initialize ZenoPayTimeoutError.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
message: Error message.
|
167
|
+
timeout_duration: The timeout duration that was exceeded.
|
168
|
+
"""
|
169
|
+
super().__init__(message)
|
170
|
+
self.timeout_duration = timeout_duration
|
171
|
+
|
172
|
+
|
173
|
+
class ZenoPayWebhookError(ZenoPayError):
|
174
|
+
"""Exception raised for webhook-related errors."""
|
175
|
+
|
176
|
+
def __init__(
|
177
|
+
self,
|
178
|
+
message: str = "Webhook processing error",
|
179
|
+
webhook_data: Optional[Dict[str, Any]] = None,
|
180
|
+
) -> None:
|
181
|
+
"""Initialize ZenoPayWebhookError.
|
182
|
+
|
183
|
+
Args:
|
184
|
+
message: Error message.
|
185
|
+
webhook_data: The webhook data that caused the error.
|
186
|
+
"""
|
187
|
+
super().__init__(message)
|
188
|
+
self.webhook_data = webhook_data or {}
|
189
|
+
|
190
|
+
|
191
|
+
def create_api_error(
|
192
|
+
status_code: int,
|
193
|
+
message: str,
|
194
|
+
response_data: Optional[Dict[str, Any]] = None,
|
195
|
+
error_code: Optional[str] = None,
|
196
|
+
) -> ZenoPayAPIError:
|
197
|
+
"""Factory function to create appropriate API error based on status code.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
status_code: HTTP status code.
|
201
|
+
message: Error message.
|
202
|
+
response_data: Response data from the API.
|
203
|
+
error_code: Specific error code from the API.
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
Appropriate ZenoPayAPIError subclass instance.
|
207
|
+
"""
|
208
|
+
if status_code == 401:
|
209
|
+
return ZenoPayAuthenticationError(message, response_data)
|
210
|
+
elif status_code == 403:
|
211
|
+
return ZenoPayAuthorizationError(message, response_data)
|
212
|
+
elif status_code == 404:
|
213
|
+
return ZenoPayNotFoundError(message, response_data)
|
214
|
+
elif status_code in (400, 422):
|
215
|
+
validation_errors = None
|
216
|
+
if response_data and "errors" in response_data:
|
217
|
+
validation_errors = response_data["errors"]
|
218
|
+
return ZenoPayValidationError(message, status_code, response_data, validation_errors)
|
219
|
+
elif status_code == 429:
|
220
|
+
retry_after = None
|
221
|
+
if response_data and "retry_after" in response_data:
|
222
|
+
retry_after = response_data["retry_after"]
|
223
|
+
return ZenoPayRateLimitError(message, response_data, retry_after)
|
224
|
+
elif status_code >= 500:
|
225
|
+
return ZenoPayServerError(message, status_code, response_data)
|
226
|
+
else:
|
227
|
+
return ZenoPayAPIError(message, status_code, response_data, error_code)
|