zenopay-sdk 0.0.1__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.
@@ -4,7 +4,7 @@ A modern Python SDK for the ZenoPay payment API with support for USSD payments,
4
4
  order management, and webhook handling.
5
5
  """
6
6
 
7
- __version__ = "0.0.1"
7
+ __version__ = "0.2.0"
8
8
  __author__ = "Elution Hub"
9
9
  __email__ = "elusion.lab@gmail.com"
10
10
 
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(account_id="zp87778")
24
+ >>> client = ZenoPayClient(api_key="your-api-key")
24
25
 
25
26
  Async usage:
26
- >>> async with ZenoPayClient(account_id="zp87778") as client:
27
+ >>> async with ZenoPayClient(api_key="your-api-key") as client:
27
28
  ... order = await client.orders.create({
28
- ... "buyer_email": "jackson@gmail.com",
29
- ... "buyer_name": "Jackson Dastani",
30
- ... "buyer_phone": "0652449389",
29
+ ... "buyer_email": "example@example.xyz",
30
+ ... "buyer_name": "example name",
31
+ ... "buyer_phone": "06XXXXXXXX",
31
32
  ... "amount": 1000,
32
- ... "webhook_url": "https://yourwebsite.com/webhook"
33
+ ... "webhook_url": "https://yourwebsite.xyz/webhook"
33
34
  ... })
34
35
 
35
36
  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",
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: {account_id}")
75
+ logger.info(f"ZenoPay client initialized for account: {api_key}")
93
76
 
94
77
  async def __aenter__(self) -> "ZenoPayClient":
95
- """Async context manager entry."""
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
- """Async context manager exit."""
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
- """Sync context manager entry."""
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
- """Sync context manager exit."""
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 account_id(self) -> str:
173
- """Get the account ID."""
174
- return self.config.account_id or ""
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(account_id='{self.account_id}', base_url='{self.base_url}')"
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
- # Default configuration
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/x-www-form-urlencoded",
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 = 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)
64
+ self.api_key = os.getenv(ENV_API_KEY) or api_key
74
65
 
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.")
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 = base_url or os.getenv(ENV_BASE_URL, DEFAULT_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}')"