paytechuz 0.2.21__py3-none-any.whl → 0.2.23__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.

Potentially problematic release.


This version of paytechuz might be problematic. Click here for more details.

core/http.py DELETED
@@ -1,268 +0,0 @@
1
- """
2
- HTTP client for making requests to payment gateways.
3
- """
4
- import json
5
- import logging
6
- from typing import Dict, Any, Optional, Union, List
7
-
8
- import requests
9
- from requests.exceptions import RequestException, Timeout, ConnectionError
10
-
11
- from paytechuz.core.exceptions import (
12
- ExternalServiceError,
13
- TimeoutError as PaymentTimeoutError,
14
- InternalServiceError
15
- )
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
- class HttpClient:
20
- """
21
- HTTP client for making requests to payment gateways.
22
-
23
- This class provides a simple interface for making HTTP requests to payment
24
- gateways with proper error handling and logging.
25
- """
26
-
27
- def __init__(
28
- self,
29
- base_url: str,
30
- headers: Optional[Dict[str, str]] = None,
31
- timeout: int = 30,
32
- verify_ssl: bool = True
33
- ):
34
- """
35
- Initialize the HTTP client.
36
-
37
- Args:
38
- base_url: Base URL for the API
39
- headers: Default headers to include in all requests
40
- timeout: Request timeout in seconds
41
- verify_ssl: Whether to verify SSL certificates
42
- """
43
- self.base_url = base_url.rstrip('/')
44
- self.headers = headers or {}
45
- self.timeout = timeout
46
- self.verify_ssl = verify_ssl
47
-
48
- def _build_url(self, endpoint: str) -> str:
49
- """
50
- Build the full URL for the given endpoint.
51
-
52
- Args:
53
- endpoint: API endpoint
54
-
55
- Returns:
56
- Full URL
57
- """
58
- endpoint = endpoint.lstrip('/')
59
- return f"{self.base_url}/{endpoint}"
60
-
61
- def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
62
- """
63
- Handle the response from the API.
64
-
65
- Args:
66
- response: Response object
67
-
68
- Returns:
69
- Response data as dictionary
70
-
71
- Raises:
72
- ExternalServiceError: If the response status code is not 2xx
73
- """
74
- try:
75
- response.raise_for_status()
76
- return response.json()
77
- except json.JSONDecodeError:
78
- logger.error(f"Failed to decode JSON response: {response.text}")
79
- raise InternalServiceError("Failed to decode JSON response")
80
- except requests.HTTPError as e:
81
- logger.error(f"HTTP error: {e}, Response: {response.text}")
82
- try:
83
- error_data = response.json()
84
- except json.JSONDecodeError:
85
- error_data = {"raw_response": response.text}
86
-
87
- raise ExternalServiceError(
88
- message=f"HTTP error: {response.status_code}",
89
- data=error_data
90
- )
91
-
92
- def request(
93
- self,
94
- method: str,
95
- endpoint: str,
96
- params: Optional[Dict[str, Any]] = None,
97
- data: Optional[Union[Dict[str, Any], List[Any]]] = None,
98
- headers: Optional[Dict[str, str]] = None,
99
- json_data: Optional[Union[Dict[str, Any], List[Any]]] = None,
100
- timeout: Optional[int] = None
101
- ) -> Dict[str, Any]:
102
- """
103
- Make an HTTP request.
104
-
105
- Args:
106
- method: HTTP method (GET, POST, PUT, DELETE, etc.)
107
- endpoint: API endpoint
108
- params: Query parameters
109
- data: Form data
110
- headers: Request headers
111
- json_data: JSON data
112
- timeout: Request timeout in seconds
113
-
114
- Returns:
115
- Response data as dictionary
116
-
117
- Raises:
118
- ExternalServiceError: If the request fails
119
- TimeoutError: If the request times out
120
- """
121
- url = self._build_url(endpoint)
122
- request_headers = {**self.headers}
123
- if headers:
124
- request_headers.update(headers)
125
-
126
- timeout = timeout or self.timeout
127
-
128
- try:
129
- response = requests.request(
130
- method=method.upper(),
131
- url=url,
132
- params=params,
133
- data=data,
134
- headers=request_headers,
135
- json=json_data,
136
- timeout=timeout,
137
- verify=self.verify_ssl
138
- )
139
- return self._handle_response(response)
140
- except Timeout:
141
- logger.error(f"Request timed out: {method} {url}")
142
- raise PaymentTimeoutError(f"Request timed out: {method} {url}")
143
- except ConnectionError as e:
144
- logger.error(f"Connection error: {e}")
145
- raise ExternalServiceError(f"Connection error: {str(e)}")
146
- except RequestException as e:
147
- logger.error(f"Request error: {e}")
148
- raise ExternalServiceError(f"Request error: {str(e)}")
149
-
150
- def get(
151
- self,
152
- endpoint: str,
153
- params: Optional[Dict[str, Any]] = None,
154
- headers: Optional[Dict[str, str]] = None,
155
- timeout: Optional[int] = None
156
- ) -> Dict[str, Any]:
157
- """
158
- Make a GET request.
159
-
160
- Args:
161
- endpoint: API endpoint
162
- params: Query parameters
163
- headers: Request headers
164
- timeout: Request timeout in seconds
165
-
166
- Returns:
167
- Response data as dictionary
168
- """
169
- return self.request(
170
- method="GET",
171
- endpoint=endpoint,
172
- params=params,
173
- headers=headers,
174
- timeout=timeout
175
- )
176
-
177
- def post(
178
- self,
179
- endpoint: str,
180
- data: Optional[Dict[str, Any]] = None,
181
- json_data: Optional[Dict[str, Any]] = None,
182
- params: Optional[Dict[str, Any]] = None,
183
- headers: Optional[Dict[str, str]] = None,
184
- timeout: Optional[int] = None
185
- ) -> Dict[str, Any]:
186
- """
187
- Make a POST request.
188
-
189
- Args:
190
- endpoint: API endpoint
191
- data: Form data
192
- json_data: JSON data
193
- params: Query parameters
194
- headers: Request headers
195
- timeout: Request timeout in seconds
196
-
197
- Returns:
198
- Response data as dictionary
199
- """
200
- return self.request(
201
- method="POST",
202
- endpoint=endpoint,
203
- data=data,
204
- json_data=json_data,
205
- params=params,
206
- headers=headers,
207
- timeout=timeout
208
- )
209
-
210
- def put(
211
- self,
212
- endpoint: str,
213
- data: Optional[Dict[str, Any]] = None,
214
- json_data: Optional[Dict[str, Any]] = None,
215
- params: Optional[Dict[str, Any]] = None,
216
- headers: Optional[Dict[str, str]] = None,
217
- timeout: Optional[int] = None
218
- ) -> Dict[str, Any]:
219
- """
220
- Make a PUT request.
221
-
222
- Args:
223
- endpoint: API endpoint
224
- data: Form data
225
- json_data: JSON data
226
- params: Query parameters
227
- headers: Request headers
228
- timeout: Request timeout in seconds
229
-
230
- Returns:
231
- Response data as dictionary
232
- """
233
- return self.request(
234
- method="PUT",
235
- endpoint=endpoint,
236
- data=data,
237
- json_data=json_data,
238
- params=params,
239
- headers=headers,
240
- timeout=timeout
241
- )
242
-
243
- def delete(
244
- self,
245
- endpoint: str,
246
- params: Optional[Dict[str, Any]] = None,
247
- headers: Optional[Dict[str, str]] = None,
248
- timeout: Optional[int] = None
249
- ) -> Dict[str, Any]:
250
- """
251
- Make a DELETE request.
252
-
253
- Args:
254
- endpoint: API endpoint
255
- params: Query parameters
256
- headers: Request headers
257
- timeout: Request timeout in seconds
258
-
259
- Returns:
260
- Response data as dictionary
261
- """
262
- return self.request(
263
- method="DELETE",
264
- endpoint=endpoint,
265
- params=params,
266
- headers=headers,
267
- timeout=timeout
268
- )
core/payme/errors.py DELETED
@@ -1,25 +0,0 @@
1
- """
2
- Payme API error codes.
3
- """
4
-
5
- # System errors
6
- SYSTEM_ERROR = -32400
7
- INVALID_JSON_RPC = -32600
8
- METHOD_NOT_FOUND = -32601
9
- INVALID_PARAMS = -32602
10
- INTERNAL_ERROR = -32603
11
-
12
- # Authorization errors
13
- AUTH_ERROR = -32504
14
- AUTH_TOKEN_INVALID = -32504
15
- AUTH_TOKEN_EXPIRED = -32504
16
-
17
- # Business logic errors
18
- INVALID_AMOUNT = -31001
19
- INVALID_ACCOUNT = -31050
20
- COULD_NOT_PERFORM = -31008
21
- COULD_NOT_CANCEL = -31007
22
- TRANSACTION_NOT_FOUND = -31003
23
- TRANSACTION_ALREADY_EXISTS = -31060
24
- TRANSACTION_ALREADY_CANCELLED = -31061
25
- TRANSACTION_ALREADY_COMPLETED = -31062
core/utils.py DELETED
@@ -1,192 +0,0 @@
1
- """
2
- Utility functions for payment gateways.
3
- """
4
- import base64
5
- import hashlib
6
- import hmac
7
- import json
8
- import logging
9
- import time
10
- from datetime import datetime
11
- from typing import Dict, Any, Union, Optional
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
- def generate_timestamp() -> int:
16
- """
17
- Generate a Unix timestamp.
18
-
19
- Returns:
20
- Current Unix timestamp in seconds
21
- """
22
- return int(time.time())
23
-
24
-
25
- def generate_id(prefix: str = "") -> str:
26
- """
27
- Generate a unique ID.
28
-
29
- Args:
30
- prefix: Prefix for the ID
31
-
32
- Returns:
33
- Unique ID
34
- """
35
- timestamp = generate_timestamp()
36
- unique_id = f"{timestamp}{hash(timestamp)}"
37
- if prefix:
38
- return f"{prefix}_{unique_id}"
39
- return unique_id
40
-
41
- def format_amount(amount: Union[int, float, str]) -> int:
42
- """
43
- Format amount to integer (in tiyin/kopeyka).
44
-
45
- Args:
46
- amount: Amount in som/ruble
47
-
48
- Returns:
49
- Amount in tiyin/kopeyka (integer)
50
- """
51
- try:
52
- # Convert to float first to handle string inputs
53
- float_amount = float(amount)
54
- # Convert to tiyin/kopeyka (multiply by 100) and round to integer
55
- return int(float_amount * 100)
56
- except (ValueError, TypeError) as e:
57
- logger.error(f"Failed to format amount: {amount}, Error: {e}")
58
- raise ValueError(f"Invalid amount format: {amount}")
59
-
60
-
61
- def format_datetime(dt: datetime) -> str:
62
- """
63
- Format datetime to ISO 8601 format.
64
-
65
- Args:
66
- dt: Datetime object
67
-
68
- Returns:
69
- Formatted datetime string
70
- """
71
- return dt.strftime("%Y-%m-%dT%H:%M:%S%z")
72
-
73
- def datetime_to_timestamp(dt: datetime) -> int:
74
- """
75
- Convert datetime to Unix timestamp.
76
-
77
- Args:
78
- dt: Datetime object
79
-
80
- Returns:
81
- Unix timestamp in seconds
82
- """
83
- return int(dt.timestamp())
84
-
85
-
86
- def timestamp_to_datetime(timestamp: int) -> datetime:
87
- """
88
- Convert Unix timestamp to datetime.
89
-
90
- Args:
91
- timestamp: Unix timestamp in seconds
92
-
93
- Returns:
94
- Datetime object
95
- """
96
- return datetime.fromtimestamp(timestamp)
97
-
98
- def generate_hmac_signature(
99
- data: Union[str, Dict[str, Any], bytes],
100
- secret_key: str,
101
- algorithm: str = "sha256"
102
- ) -> str:
103
- """
104
- Generate HMAC signature.
105
-
106
- Args:
107
- data: Data to sign
108
- secret_key: Secret key for signing
109
- algorithm: Hash algorithm to use
110
-
111
- Returns:
112
- HMAC signature as hexadecimal string
113
- """
114
- if isinstance(data, dict):
115
- data = json.dumps(data, separators=(',', ':'))
116
-
117
- if isinstance(data, str):
118
- data = data.encode('utf-8')
119
-
120
- key = secret_key.encode('utf-8')
121
-
122
- if algorithm.lower() == "sha256":
123
- signature = hmac.new(key, data, hashlib.sha256).hexdigest()
124
- elif algorithm.lower() == "sha512":
125
- signature = hmac.new(key, data, hashlib.sha512).hexdigest()
126
- elif algorithm.lower() == "md5":
127
- signature = hmac.new(key, data, hashlib.md5).hexdigest()
128
- else:
129
- raise ValueError(f"Unsupported algorithm: {algorithm}")
130
-
131
- return signature
132
-
133
-
134
- def generate_basic_auth(username: str, password: str) -> str:
135
- """
136
- Generate Basic Authentication header value.
137
-
138
- Args:
139
- username: Username
140
- password: Password
141
-
142
- Returns:
143
- Basic Authentication header value
144
- """
145
- auth_str = f"{username}:{password}"
146
- auth_bytes = auth_str.encode('utf-8')
147
- encoded = base64.b64encode(auth_bytes).decode('utf-8')
148
- return f"Basic {encoded}"
149
-
150
- def handle_exceptions(func):
151
- """
152
- Decorator to handle exceptions and convert them to payment exceptions.
153
-
154
- Args:
155
- func: Function to decorate
156
-
157
- Returns:
158
- Decorated function
159
- """
160
- from paytechuz.core.exceptions import (
161
- InternalServiceError,
162
- exception_whitelist
163
- )
164
-
165
- def wrapper(*args, **kwargs):
166
- try:
167
- return func(*args, **kwargs)
168
- except exception_whitelist as exc:
169
- # No need to wrap exceptions that are already payment exceptions
170
- raise exc
171
- except Exception as exc:
172
- logger.exception(f"Unexpected error in {func.__name__}: {exc}")
173
- raise InternalServiceError(str(exc))
174
-
175
- return wrapper
176
-
177
-
178
- def validate_required_fields(data: Dict[str, Any], required_fields: list) -> Optional[str]:
179
- """
180
- Validate that all required fields are present in the data.
181
-
182
- Args:
183
- data: Data to validate
184
- required_fields: List of required field names
185
-
186
- Returns:
187
- Error message if validation fails, None otherwise
188
- """
189
- missing_fields = [field for field in required_fields if field not in data or data[field] is None]
190
- if missing_fields:
191
- return f"Missing required fields: {', '.join(missing_fields)}"
192
- return None
gateways/__init__.py DELETED
File without changes
File without changes
gateways/click/client.py DELETED
@@ -1,199 +0,0 @@
1
- """
2
- Click payment gateway client.
3
- """
4
- import logging
5
- from typing import Dict, Any, Optional, Union
6
-
7
- from paytechuz.core.base import BasePaymentGateway
8
- from paytechuz.core.http import HttpClient
9
- from paytechuz.core.constants import ClickNetworks
10
- from paytechuz.core.utils import handle_exceptions
11
- from paytechuz.gateways.click.merchant import ClickMerchantApi
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class ClickGateway(BasePaymentGateway):
17
- """
18
- Click payment gateway implementation.
19
-
20
- This class provides methods for interacting with the Click payment gateway,
21
- including creating payments, checking payment status, and canceling payments.
22
- """
23
-
24
- def __init__(
25
- self,
26
- service_id: str,
27
- merchant_id: str,
28
- merchant_user_id: Optional[str] = None,
29
- secret_key: Optional[str] = None,
30
- is_test_mode: bool = False
31
- ):
32
- """
33
- Initialize the Click gateway.
34
-
35
- Args:
36
- service_id: Click service ID
37
- merchant_id: Click merchant ID
38
- merchant_user_id: Click merchant user ID
39
- secret_key: Secret key for authentication
40
- is_test_mode: Whether to use the test environment
41
- """
42
- super().__init__(is_test_mode)
43
- self.service_id = service_id
44
- self.merchant_id = merchant_id
45
- self.merchant_user_id = merchant_user_id
46
- self.secret_key = secret_key
47
-
48
- # Set the API URL based on the environment
49
- url = ClickNetworks.TEST_NET if is_test_mode else ClickNetworks.PROD_NET
50
-
51
- # Initialize HTTP client
52
- self.http_client = HttpClient(base_url=url)
53
-
54
- # Initialize merchant API
55
- self.merchant_api = ClickMerchantApi(
56
- http_client=self.http_client,
57
- service_id=service_id,
58
- merchant_user_id=merchant_user_id,
59
- secret_key=secret_key
60
- )
61
-
62
- @handle_exceptions
63
- def create_payment(
64
- self,
65
- id: Union[int, str],
66
- amount: Union[int, float, str],
67
- **kwargs
68
- ) -> str:
69
- """
70
- Create a payment using Click.
71
-
72
- Args:
73
- id: The account ID or order ID
74
- amount: The payment amount in som
75
- **kwargs: Additional parameters for the payment
76
- - description: Payment description
77
- - return_url: URL to return after payment
78
- - callback_url: URL for payment notifications
79
- - language: Language code (uz, ru, en)
80
- - phone: Customer phone number
81
- - email: Customer email
82
-
83
- Returns:
84
- Payment URL string for redirecting the user to Click payment page
85
- """
86
- # Format amount for URL (no need to convert to tiyin for URL)
87
-
88
- # Extract additional parameters
89
- description = kwargs.get('description', f'Payment for account {id}')
90
- return_url = kwargs.get('return_url')
91
- callback_url = kwargs.get('callback_url')
92
- # These parameters are not used in the URL but are available in the API
93
- # language = kwargs.get('language', 'uz')
94
- # phone = kwargs.get('phone')
95
- # email = kwargs.get('email')
96
-
97
- # Create payment URL
98
- payment_url = "https://my.click.uz/services/pay"
99
- payment_url += f"?service_id={self.service_id}"
100
- payment_url += f"&merchant_id={self.merchant_id}"
101
- payment_url += f"&amount={amount}"
102
- payment_url += f"&transaction_param={id}"
103
-
104
- if return_url:
105
- payment_url += f"&return_url={return_url}"
106
-
107
- if callback_url:
108
- payment_url += f"&callback_url={callback_url}"
109
-
110
- if description:
111
- payment_url += f"&description={description}"
112
-
113
- if self.merchant_user_id:
114
- payment_url += f"&merchant_user_id={self.merchant_user_id}"
115
-
116
- # Return the payment URL directly
117
- return payment_url
118
-
119
- @handle_exceptions
120
- def check_payment(self, transaction_id: str) -> Dict[str, Any]:
121
- """
122
- Check payment status using Click merchant API.
123
-
124
- Args:
125
- transaction_id: The transaction ID to check
126
-
127
- Returns:
128
- Dict containing payment status and details
129
- """
130
- # Extract account_id from transaction_id
131
- # Format: click_account_id_amount
132
- parts = transaction_id.split('_')
133
- if len(parts) < 3 or parts[0] != 'click':
134
- raise ValueError(
135
- f"Invalid transaction ID format: {transaction_id}"
136
- )
137
-
138
- account_id = parts[1]
139
-
140
- # Check payment status using merchant API
141
- payment_data = self.merchant_api.check_payment(account_id)
142
-
143
- # Extract payment status
144
- status = payment_data.get('status')
145
-
146
- # Map Click status to our status
147
- status_mapping = {
148
- 'success': 'paid',
149
- 'processing': 'waiting',
150
- 'failed': 'failed',
151
- 'cancelled': 'cancelled'
152
- }
153
-
154
- mapped_status = status_mapping.get(status, 'unknown')
155
-
156
- return {
157
- 'transaction_id': transaction_id,
158
- 'status': mapped_status,
159
- 'amount': payment_data.get('amount'),
160
- 'paid_at': payment_data.get('paid_at'),
161
- 'created_at': payment_data.get('created_at'),
162
- 'raw_response': payment_data
163
- }
164
-
165
- @handle_exceptions
166
- def cancel_payment(
167
- self,
168
- transaction_id: str,
169
- reason: Optional[str] = None
170
- ) -> Dict[str, Any]:
171
- """
172
- Cancel payment using Click merchant API.
173
-
174
- Args:
175
- transaction_id: The transaction ID to cancel
176
- reason: Optional reason for cancellation
177
-
178
- Returns:
179
- Dict containing cancellation status and details
180
- """
181
- # Extract account_id from transaction_id
182
- # Format: click_account_id_amount
183
- parts = transaction_id.split('_')
184
- if len(parts) < 3 or parts[0] != 'click':
185
- raise ValueError(
186
- f"Invalid transaction ID format: {transaction_id}"
187
- )
188
-
189
- account_id = parts[1]
190
-
191
- # Cancel payment using merchant API
192
- cancel_data = self.merchant_api.cancel_payment(account_id, reason)
193
-
194
- return {
195
- 'transaction_id': transaction_id,
196
- 'status': 'cancelled',
197
- 'cancelled_at': cancel_data.get('cancelled_at'),
198
- 'raw_response': cancel_data
199
- }