paytechuz 0.2.19__py3-none-any.whl → 0.2.20__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/__init__.py ADDED
File without changes
core/base.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ Base classes for payment gateways.
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from typing import Dict, Any, Optional, Union
6
+
7
+
8
+ class BasePaymentGateway(ABC):
9
+ """
10
+ Base class for all payment gateways.
11
+
12
+ This abstract class defines the common interface that all payment gateways
13
+ must implement. It provides a consistent API for creating, checking, and
14
+ canceling payments regardless of the underlying payment provider.
15
+ """
16
+
17
+ def __init__(self, is_test_mode: bool = False):
18
+ """
19
+ Initialize the payment gateway.
20
+
21
+ Args:
22
+ is_test_mode (bool): Whether to use the test environment
23
+ """
24
+ self.is_test_mode = is_test_mode
25
+
26
+ @abstractmethod
27
+ def create_payment(
28
+ self,
29
+ id: Union[int, str],
30
+ amount: Union[int, float, str],
31
+ **kwargs
32
+ ) -> Dict[str, Any]:
33
+ """
34
+ Create a payment.
35
+
36
+ Args:
37
+ id: The account ID or order ID
38
+ amount: The payment amount
39
+ **kwargs: Additional parameters specific to the payment gateway
40
+
41
+ Returns:
42
+ Dict containing payment details including transaction ID
43
+ """
44
+ raise NotImplementedError
45
+
46
+ @abstractmethod
47
+ def check_payment(self, transaction_id: str) -> Dict[str, Any]:
48
+ """
49
+ Check payment status.
50
+
51
+ Args:
52
+ transaction_id: The transaction ID to check
53
+
54
+ Returns:
55
+ Dict containing payment status and details
56
+ """
57
+ raise NotImplementedError
58
+
59
+ @abstractmethod
60
+ def cancel_payment(
61
+ self,
62
+ transaction_id: str,
63
+ reason: Optional[str] = None
64
+ ) -> Dict[str, Any]:
65
+ """
66
+ Cancel payment.
67
+
68
+ Args:
69
+ transaction_id: The transaction ID to cancel
70
+ reason: Optional reason for cancellation
71
+
72
+ Returns:
73
+ Dict containing cancellation status and details
74
+ """
75
+ raise NotImplementedError
76
+
77
+
78
+ class BaseWebhookHandler(ABC):
79
+ """
80
+ Base class for payment gateway webhook handlers.
81
+
82
+ This abstract class defines the common interface for handling webhook
83
+ callbacks from payment gateways.
84
+ """
85
+
86
+ @abstractmethod
87
+ def handle_webhook(self, data: Dict[str, Any]) -> Dict[str, Any]:
88
+ """
89
+ Handle webhook data from payment gateway.
90
+
91
+ Args:
92
+ data: The webhook data received from the payment gateway
93
+
94
+ Returns:
95
+ Dict containing the response to be sent back to the payment gateway
96
+ """
97
+ raise NotImplementedError
core/constants.py ADDED
@@ -0,0 +1,68 @@
1
+ """
2
+ Constants for payment gateways.
3
+ """
4
+ from enum import Enum
5
+
6
+ class TransactionState(Enum):
7
+ """Transaction states."""
8
+ CREATED = 0
9
+ INITIATING = 1
10
+ SUCCESSFULLY = 2
11
+ CANCELED = -2
12
+ CANCELED_DURING_INIT = -1
13
+
14
+
15
+ class PaymentGateway(Enum):
16
+ """Payment gateway types."""
17
+ PAYME = "payme"
18
+ CLICK = "click"
19
+
20
+ class PaymeEndpoints:
21
+ """Payme API endpoints."""
22
+ RECEIPTS_CREATE = "receipts/create"
23
+ RECEIPTS_PAY = "receipts/pay"
24
+ RECEIPTS_SEND = "receipts/send"
25
+ RECEIPTS_CHECK = "receipts/check"
26
+ RECEIPTS_CANCEL = "receipts/cancel"
27
+ RECEIPTS_GET = "receipts/get"
28
+ CARDS_CREATE = "cards/create"
29
+ CARDS_VERIFY = "cards/verify"
30
+ CARDS_CHECK = "cards/check"
31
+ CARDS_REMOVE = "cards/remove"
32
+ CARDS_GET_VERIFY_CODE = "cards/get_verify_code"
33
+
34
+
35
+ class PaymeNetworks:
36
+ """Payme API networks."""
37
+ TEST_NET = "https://checkout.test.paycom.uz/api"
38
+ PROD_NET = "https://checkout.paycom.uz/api"
39
+
40
+ class ClickEndpoints:
41
+ """Click API endpoints."""
42
+ PREPARE = "prepare"
43
+ COMPLETE = "complete"
44
+ MERCHANT_API = "merchant/api"
45
+
46
+
47
+ class ClickNetworks:
48
+ """Click API networks."""
49
+ TEST_NET = "https://api.click.uz/v2/merchant"
50
+ PROD_NET = "https://api.click.uz/v2/merchant"
51
+
52
+ class ClickActions:
53
+ """Click API actions."""
54
+ PREPARE = 0
55
+ COMPLETE = 1
56
+
57
+
58
+ class PaymeCancelReason:
59
+ """Payme cancel reason codes."""
60
+ REASON_USER_NOT_FOUND = 1
61
+ REASON_DEBIT_OPERATION_FAILED = 2
62
+ REASON_EXECUTION_ERROR = 3
63
+ REASON_TIMEOUT = 4
64
+ REASON_FUND_RETURNED = 5
65
+ REASON_UNKNOWN = 6
66
+ REASON_CANCELLED_BY_USER = 7
67
+ REASON_SUSPICIOUS_OPERATION = 8
68
+ REASON_MERCHANT_DECISION = 9
core/exceptions.py ADDED
@@ -0,0 +1,190 @@
1
+ """
2
+ Exceptions for payment gateways.
3
+ """
4
+ from typing import Optional, Dict, Any
5
+
6
+ class PaymentException(Exception):
7
+ """Base exception for all payment exceptions."""
8
+ code = "payment_error"
9
+ message = "Payment error occurred"
10
+ data: Dict[str, Any] = {}
11
+
12
+ def __init__(self, message: Optional[str] = None, code: Optional[str] = None, data: Optional[Dict[str, Any]] = None):
13
+ """
14
+ Initialize the exception.
15
+
16
+ Args:
17
+ message: Custom error message
18
+ code: Custom error code
19
+ data: Additional error data
20
+ """
21
+ self.message = message or self.message
22
+ self.code = code or self.code
23
+ self.data = data or self.data
24
+ super().__init__(self.message)
25
+
26
+ def as_dict(self) -> Dict[str, Any]:
27
+ """
28
+ Convert the exception to a dictionary.
29
+
30
+ Returns:
31
+ Dict containing error details
32
+ """
33
+ return {
34
+ "code": self.code,
35
+ "message": self.message,
36
+ "data": self.data
37
+ }
38
+
39
+ # Authentication and Authorization Exceptions
40
+ class AuthenticationError(PaymentException):
41
+ """Exception raised when authentication fails."""
42
+ code = "authentication_error"
43
+ message = "Authentication failed"
44
+
45
+
46
+
47
+ class InvalidCredentials(AuthenticationError):
48
+ """Exception raised when credentials are invalid."""
49
+ code = "invalid_credentials"
50
+ message = "Invalid credentials provided"
51
+
52
+ class PermissionDenied(AuthenticationError):
53
+ """Exception raised when permission is denied."""
54
+ code = "permission_denied"
55
+ message = "Permission denied"
56
+
57
+ # Transaction Exceptions
58
+ class TransactionError(PaymentException):
59
+ """Base exception for transaction errors."""
60
+ code = "transaction_error"
61
+ message = "Transaction error occurred"
62
+
63
+
64
+ class TransactionNotFound(TransactionError):
65
+ """Exception raised when a transaction is not found."""
66
+ code = "transaction_not_found"
67
+ message = "Transaction not found"
68
+
69
+ class TransactionAlreadyExists(TransactionError):
70
+ """Exception raised when a transaction already exists."""
71
+ code = "transaction_already_exists"
72
+ message = "Transaction already exists"
73
+
74
+
75
+ class TransactionCancelled(TransactionError):
76
+ """Exception raised when a transaction is cancelled."""
77
+ code = "transaction_cancelled"
78
+ message = "Transaction has been cancelled"
79
+
80
+ class TransactionInProgress(TransactionError):
81
+ """Exception raised when a transaction is in progress."""
82
+ code = "transaction_in_progress"
83
+ message = "Transaction is in progress"
84
+
85
+
86
+ class TransactionCompleted(TransactionError):
87
+ """Exception raised when a transaction is already completed."""
88
+ code = "transaction_completed"
89
+ message = "Transaction is already completed"
90
+
91
+ # Account Exceptions
92
+ class AccountError(PaymentException):
93
+ """Base exception for account errors."""
94
+ code = "account_error"
95
+ message = "Account error occurred"
96
+
97
+
98
+ class AccountNotFound(AccountError):
99
+ """Exception raised when an account is not found."""
100
+ code = "account_not_found"
101
+ message = "Account not found"
102
+
103
+ class InvalidAccount(AccountError):
104
+ """Exception raised when an account is invalid."""
105
+ code = "invalid_account"
106
+ message = "Invalid account"
107
+
108
+ # Amount Exceptions
109
+ class AmountError(PaymentException):
110
+ """Base exception for amount errors."""
111
+ code = "amount_error"
112
+ message = "Amount error occurred"
113
+
114
+
115
+ class InvalidAmount(AmountError):
116
+ """Exception raised when an amount is invalid."""
117
+ code = "invalid_amount"
118
+ message = "Invalid amount"
119
+
120
+ class InsufficientFunds(AmountError):
121
+ """Exception raised when there are insufficient funds."""
122
+ code = "insufficient_funds"
123
+ message = "Insufficient funds"
124
+
125
+ # Method Exceptions
126
+ class MethodError(PaymentException):
127
+ """Base exception for method errors."""
128
+ code = "method_error"
129
+ message = "Method error occurred"
130
+
131
+
132
+ class MethodNotFound(MethodError):
133
+ """Exception raised when a method is not found."""
134
+ code = "method_not_found"
135
+ message = "Method not found"
136
+
137
+ class UnsupportedMethod(MethodError):
138
+ """Exception raised when a method is not supported."""
139
+ code = "unsupported_method"
140
+ message = "Method not supported"
141
+
142
+ # System Exceptions
143
+ class SystemError(PaymentException):
144
+ """Base exception for system errors."""
145
+ code = "system_error"
146
+ message = "System error occurred"
147
+
148
+
149
+ class InternalServiceError(SystemError):
150
+ """Exception raised when an internal service error occurs."""
151
+ code = "internal_service_error"
152
+ message = "Internal service error"
153
+
154
+ class ExternalServiceError(SystemError):
155
+ """Exception raised when an external service error occurs."""
156
+ code = "external_service_error"
157
+ message = "External service error"
158
+
159
+
160
+ class TimeoutError(SystemError):
161
+ """Exception raised when a timeout occurs."""
162
+ code = "timeout_error"
163
+ message = "Operation timed out"
164
+
165
+ # Create a list of exceptions that should not be wrapped
166
+ exception_whitelist = (
167
+ PaymentException,
168
+ AuthenticationError,
169
+ InvalidCredentials,
170
+ PermissionDenied,
171
+ TransactionError,
172
+ TransactionNotFound,
173
+ TransactionAlreadyExists,
174
+ TransactionCancelled,
175
+ TransactionInProgress,
176
+ TransactionCompleted,
177
+ AccountError,
178
+ AccountNotFound,
179
+ InvalidAccount,
180
+ AmountError,
181
+ InvalidAmount,
182
+ InsufficientFunds,
183
+ MethodError,
184
+ MethodNotFound,
185
+ UnsupportedMethod,
186
+ SystemError,
187
+ InternalServiceError,
188
+ ExternalServiceError,
189
+ TimeoutError,
190
+ )
core/http.py ADDED
@@ -0,0 +1,268 @@
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 ADDED
@@ -0,0 +1,25 @@
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