paytechuz 0.2.21__py3-none-any.whl → 0.2.22__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.
- {paytechuz-0.2.21.dist-info → paytechuz-0.2.22.dist-info}/METADATA +6 -33
- {paytechuz-0.2.21.dist-info → paytechuz-0.2.22.dist-info}/RECORD +4 -35
- {paytechuz-0.2.21.dist-info → paytechuz-0.2.22.dist-info}/WHEEL +1 -1
- paytechuz-0.2.22.dist-info/top_level.txt +1 -0
- core/__init__.py +0 -0
- core/base.py +0 -97
- core/constants.py +0 -68
- core/exceptions.py +0 -190
- core/http.py +0 -268
- core/payme/errors.py +0 -25
- core/utils.py +0 -192
- gateways/__init__.py +0 -0
- gateways/click/__init__.py +0 -0
- gateways/click/client.py +0 -199
- gateways/click/merchant.py +0 -265
- gateways/click/webhook.py +0 -227
- gateways/payme/__init__.py +0 -0
- gateways/payme/cards.py +0 -222
- gateways/payme/client.py +0 -262
- gateways/payme/receipts.py +0 -336
- gateways/payme/webhook.py +0 -379
- integrations/__init__.py +0 -0
- integrations/django/__init__.py +0 -4
- integrations/django/admin.py +0 -78
- integrations/django/apps.py +0 -21
- integrations/django/migrations/0001_initial.py +0 -51
- integrations/django/migrations/__init__.py +0 -3
- integrations/django/models.py +0 -174
- integrations/django/signals.py +0 -46
- integrations/django/views.py +0 -102
- integrations/django/webhooks.py +0 -884
- integrations/fastapi/__init__.py +0 -21
- integrations/fastapi/models.py +0 -183
- integrations/fastapi/routes.py +0 -1038
- integrations/fastapi/schemas.py +0 -116
- paytechuz-0.2.21.dist-info/top_level.txt +0 -4
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
|
gateways/click/__init__.py
DELETED
|
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
|
-
}
|