paytechuz 0.2.20__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.

@@ -1,265 +0,0 @@
1
- """
2
- Click merchant API operations.
3
- """
4
- import hashlib
5
- import logging
6
- from typing import Dict, Any, Optional, Union
7
-
8
- from paytechuz.core.http import HttpClient
9
- from paytechuz.core.constants import ClickEndpoints
10
- from paytechuz.core.utils import handle_exceptions, generate_timestamp
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class ClickMerchantApi:
16
- """
17
- Click merchant API operations.
18
-
19
- This class provides methods for interacting with the Click merchant API,
20
- including checking payment status and canceling payments.
21
- """
22
-
23
- def __init__(
24
- self,
25
- http_client: HttpClient,
26
- service_id: str,
27
- merchant_user_id: Optional[str] = None,
28
- secret_key: Optional[str] = None
29
- ):
30
- """
31
- Initialize the Click merchant API.
32
-
33
- Args:
34
- http_client: HTTP client for making requests
35
- service_id: Click service ID
36
- merchant_user_id: Click merchant user ID
37
- secret_key: Secret key for authentication
38
- """
39
- self.http_client = http_client
40
- self.service_id = service_id
41
- self.merchant_user_id = merchant_user_id
42
- self.secret_key = secret_key
43
-
44
- def _generate_signature(self, data: Dict[str, Any]) -> str:
45
- """
46
- Generate signature for Click API requests.
47
-
48
- Args:
49
- data: Request data
50
-
51
- Returns:
52
- Signature string
53
- """
54
- if not self.secret_key:
55
- return ""
56
-
57
- # Sort keys alphabetically
58
- sorted_data = {k: data[k] for k in sorted(data.keys())}
59
-
60
- # Create string to sign
61
- sign_string = ""
62
- for key, value in sorted_data.items():
63
- if key != "sign":
64
- sign_string += str(value)
65
-
66
- # Add secret key
67
- sign_string += self.secret_key
68
-
69
- # Generate signature
70
- return hashlib.md5(sign_string.encode('utf-8')).hexdigest()
71
-
72
- @handle_exceptions
73
- def check_payment(self, id: Union[int, str]) -> Dict[str, Any]:
74
- """
75
- Check payment status.
76
-
77
- Args:
78
- account_id: Account ID or order ID
79
-
80
- Returns:
81
- Dict containing payment status and details
82
- """
83
- # Prepare request data
84
- data = {
85
- "service_id": self.service_id,
86
- "merchant_transaction_id": str(id),
87
- "request_id": str(generate_timestamp())
88
- }
89
-
90
- # Add signature if secret key is provided
91
- if self.secret_key:
92
- data["sign"] = self._generate_signature(data)
93
-
94
- # Make request
95
- response = self.http_client.post(
96
- endpoint=f"{ClickEndpoints.MERCHANT_API}/payment/status",
97
- json_data=data
98
- )
99
-
100
- return response
101
-
102
- @handle_exceptions
103
- def cancel_payment(
104
- self,
105
- id: Union[int, str],
106
- reason: Optional[str] = None
107
- ) -> Dict[str, Any]:
108
- """
109
- Cancel payment.
110
-
111
- Args:
112
- id: Account ID or order ID
113
- reason: Optional reason for cancellation
114
-
115
- Returns:
116
- Dict containing cancellation status and details
117
- """
118
- # Prepare request data
119
- data = {
120
- "service_id": self.service_id,
121
- "merchant_transaction_id": str(id),
122
- "request_id": str(generate_timestamp())
123
- }
124
-
125
- # Add reason if provided
126
- if reason:
127
- data["reason"] = reason
128
-
129
- # Add signature if secret key is provided
130
- if self.secret_key:
131
- data["sign"] = self._generate_signature(data)
132
-
133
- # Make request
134
- response = self.http_client.post(
135
- endpoint=f"{ClickEndpoints.MERCHANT_API}/payment/cancel",
136
- json_data=data
137
- )
138
-
139
- return response
140
-
141
- @handle_exceptions
142
- def create_invoice(
143
- self,
144
- id: Union[int, str],
145
- amount: Union[int, float],
146
- **kwargs
147
- ) -> Dict[str, Any]:
148
- """
149
- Create an invoice.
150
-
151
- Args:
152
- amount: Payment amount
153
- id: Account ID or order ID
154
- **kwargs: Additional parameters
155
- - description: Payment description
156
- - phone: Customer phone number
157
- - email: Customer email
158
- - expire_time: Invoice expiration time in minutes
159
-
160
- Returns:
161
- Dict containing invoice details
162
- """
163
- # Extract additional parameters
164
- description = kwargs.get('description', f'Payment for account {id}')
165
- phone = kwargs.get('phone')
166
- email = kwargs.get('email')
167
- expire_time = kwargs.get('expire_time', 60) # Default 1 hour
168
-
169
- # Prepare request data
170
- data = {
171
- "service_id": self.service_id,
172
- "amount": float(amount),
173
- "merchant_transaction_id": str(id),
174
- "description": description,
175
- "request_id": str(generate_timestamp()),
176
- "expire_time": expire_time
177
- }
178
-
179
- # Add optional parameters
180
- if phone:
181
- data["phone"] = phone
182
-
183
- if email:
184
- data["email"] = email
185
-
186
- # Add signature if secret key is provided
187
- if self.secret_key:
188
- data["sign"] = self._generate_signature(data)
189
-
190
- # Make request
191
- response = self.http_client.post(
192
- endpoint=f"{ClickEndpoints.MERCHANT_API}/invoice/create",
193
- json_data=data
194
- )
195
-
196
- return response
197
-
198
- @handle_exceptions
199
- def check_invoice(self, invoice_id: str) -> Dict[str, Any]:
200
- """
201
- Check invoice status.
202
-
203
- Args:
204
- invoice_id: Invoice ID
205
-
206
- Returns:
207
- Dict containing invoice status and details
208
- """
209
- # Prepare request data
210
- data = {
211
- "service_id": self.service_id,
212
- "invoice_id": invoice_id,
213
- "request_id": str(generate_timestamp())
214
- }
215
-
216
- # Add signature if secret key is provided
217
- if self.secret_key:
218
- data["sign"] = self._generate_signature(data)
219
-
220
- # Make request
221
- response = self.http_client.post(
222
- endpoint=f"{ClickEndpoints.MERCHANT_API}/invoice/status",
223
- json_data=data
224
- )
225
-
226
- return response
227
-
228
- @handle_exceptions
229
- def cancel_invoice(
230
- self,
231
- invoice_id: str,
232
- reason: Optional[str] = None
233
- ) -> Dict[str, Any]:
234
- """
235
- Cancel invoice.
236
-
237
- Args:
238
- invoice_id: Invoice ID
239
- reason: Optional reason for cancellation
240
-
241
- Returns:
242
- Dict containing cancellation status and details
243
- """
244
- # Prepare request data
245
- data = {
246
- "service_id": self.service_id,
247
- "invoice_id": invoice_id,
248
- "request_id": str(generate_timestamp())
249
- }
250
-
251
- # Add reason if provided
252
- if reason:
253
- data["reason"] = reason
254
-
255
- # Add signature if secret key is provided
256
- if self.secret_key:
257
- data["sign"] = self._generate_signature(data)
258
-
259
- # Make request
260
- response = self.http_client.post(
261
- endpoint=f"{ClickEndpoints.MERCHANT_API}/invoice/cancel",
262
- json_data=data
263
- )
264
-
265
- return response
gateways/click/webhook.py DELETED
@@ -1,227 +0,0 @@
1
- """
2
- Click webhook handler.
3
- """
4
- import hashlib
5
- import logging
6
- from typing import Dict, Any, Callable
7
-
8
- from paytechuz.core.base import BaseWebhookHandler
9
- from paytechuz.core.constants import ClickActions
10
- from paytechuz.core.exceptions import (
11
- PermissionDenied,
12
- InvalidAmount,
13
- TransactionNotFound,
14
- UnsupportedMethod,
15
- AccountNotFound
16
- )
17
- from paytechuz.core.utils import handle_exceptions
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
- class ClickWebhookHandler(BaseWebhookHandler):
22
- """
23
- Click webhook handler.
24
-
25
- This class handles webhook requests from the Click payment system,
26
- including transaction preparation and completion.
27
- """
28
-
29
- def __init__(
30
- self,
31
- service_id: str,
32
- secret_key: str,
33
- find_transaction_func: Callable[[str], Dict[str, Any]],
34
- find_account_func: Callable[[str], Dict[str, Any]],
35
- create_transaction_func: Callable[[Dict[str, Any]], Dict[str, Any]],
36
- complete_transaction_func: Callable[[str, bool], Dict[str, Any]],
37
- commission_percent: float = 0.0
38
- ):
39
- """
40
- Initialize the Click webhook handler.
41
-
42
- Args:
43
- service_id: Click service ID
44
- secret_key: Secret key for authentication
45
- find_transaction_func: Function to find a transaction by ID
46
- find_account_func: Function to find an account by ID
47
- create_transaction_func: Function to create a transaction
48
- complete_transaction_func: Function to complete a transaction
49
- commission_percent: Commission percentage
50
- """
51
- self.service_id = service_id
52
- self.secret_key = secret_key
53
- self.find_transaction = find_transaction_func
54
- self.find_account = find_account_func
55
- self.create_transaction = create_transaction_func
56
- self.complete_transaction = complete_transaction_func
57
- self.commission_percent = commission_percent
58
-
59
- def _check_auth(self, params: Dict[str, Any]) -> None:
60
- """
61
- Check authentication using signature.
62
-
63
- Args:
64
- params: Request parameters
65
-
66
- Raises:
67
- PermissionDenied: If authentication fails
68
- """
69
- if str(params.get('service_id')) != self.service_id:
70
- raise PermissionDenied("Invalid service ID")
71
-
72
- # Check signature if secret key is provided
73
- if self.secret_key:
74
- sign_string = params.get('sign_string')
75
- sign_time = params.get('sign_time')
76
-
77
- if not sign_string or not sign_time:
78
- raise PermissionDenied("Missing signature parameters")
79
-
80
- # Create string to sign
81
- to_sign = f"{params.get('click_trans_id')}{params.get('service_id')}"
82
- to_sign += f"{self.secret_key}{params.get('merchant_trans_id')}"
83
- to_sign += f"{params.get('amount')}{params.get('action')}"
84
- to_sign += f"{sign_time}"
85
-
86
- # Generate signature
87
- signature = hashlib.md5(to_sign.encode('utf-8')).hexdigest()
88
-
89
- if signature != sign_string:
90
- raise PermissionDenied("Invalid signature")
91
-
92
- def _validate_amount(
93
- self,
94
- received_amount: float,
95
- expected_amount: float
96
- ) -> None:
97
- """
98
- Validate payment amount.
99
-
100
- Args:
101
- received_amount: Amount received from Click
102
- expected_amount: Expected amount
103
-
104
- Raises:
105
- InvalidAmount: If amounts don't match
106
- """
107
- # Add commission if needed
108
- if self.commission_percent > 0:
109
- expected_amount = expected_amount * (1 + self.commission_percent / 100)
110
- expected_amount = round(expected_amount, 2)
111
-
112
- # Allow small difference due to floating point precision
113
- if abs(received_amount - expected_amount) > 0.01:
114
- raise InvalidAmount(f"Incorrect amount. Expected: {expected_amount}, received: {received_amount}")
115
-
116
- @handle_exceptions
117
- def handle_webhook(self, data: Dict[str, Any]) -> Dict[str, Any]:
118
- """
119
- Handle webhook data from Click.
120
-
121
- Args:
122
- data: The webhook data received from Click
123
-
124
- Returns:
125
- Dict containing the response to be sent back to Click
126
-
127
- Raises:
128
- PermissionDenied: If authentication fails
129
- UnsupportedMethod: If the requested action is not supported
130
- """
131
- # Check authentication
132
- self._check_auth(data)
133
-
134
- # Extract parameters
135
- click_trans_id = data.get('click_trans_id')
136
- merchant_trans_id = data.get('merchant_trans_id')
137
- amount = float(data.get('amount', 0))
138
- action = int(data.get('action', -1))
139
- error = int(data.get('error', 0))
140
-
141
- # Find account
142
- try:
143
- account = self.find_account(merchant_trans_id)
144
- except AccountNotFound:
145
- logger.error(f"Account not found: {merchant_trans_id}")
146
- return {
147
- 'click_trans_id': click_trans_id,
148
- 'merchant_trans_id': merchant_trans_id,
149
- 'error': -5,
150
- 'error_note': "User not found"
151
- }
152
-
153
- # Validate amount
154
- try:
155
- self._validate_amount(amount, float(account.get('amount', 0)))
156
- except InvalidAmount as e:
157
- logger.error(f"Invalid amount: {e}")
158
- return {
159
- 'click_trans_id': click_trans_id,
160
- 'merchant_trans_id': merchant_trans_id,
161
- 'error': -2,
162
- 'error_note': str(e)
163
- }
164
-
165
- # Check if transaction already exists
166
- try:
167
- transaction = self.find_transaction(click_trans_id)
168
-
169
- # If transaction is already completed, return success
170
- if transaction.get('state') == 2: # SUCCESSFULLY
171
- return {
172
- 'click_trans_id': click_trans_id,
173
- 'merchant_trans_id': merchant_trans_id,
174
- 'merchant_prepare_id': transaction.get('id'),
175
- 'error': 0,
176
- 'error_note': "Success"
177
- }
178
-
179
- # If transaction is cancelled, return error
180
- if transaction.get('state') == -2: # CANCELLED
181
- return {
182
- 'click_trans_id': click_trans_id,
183
- 'merchant_trans_id': merchant_trans_id,
184
- 'merchant_prepare_id': transaction.get('id'),
185
- 'error': -9,
186
- 'error_note': "Transaction cancelled"
187
- }
188
- except TransactionNotFound:
189
- # Transaction doesn't exist, continue with the flow
190
- pass
191
-
192
- # Handle different actions
193
- if action == ClickActions.PREPARE:
194
- # Create transaction
195
- transaction = self.create_transaction({
196
- 'click_trans_id': click_trans_id,
197
- 'merchant_trans_id': merchant_trans_id,
198
- 'amount': amount,
199
- 'account': account
200
- })
201
-
202
- return {
203
- 'click_trans_id': click_trans_id,
204
- 'merchant_trans_id': merchant_trans_id,
205
- 'merchant_prepare_id': transaction.get('id'),
206
- 'error': 0,
207
- 'error_note': "Success"
208
- }
209
-
210
- elif action == ClickActions.COMPLETE:
211
- # Check if error is negative (payment failed)
212
- is_successful = error >= 0
213
-
214
- # Complete transaction
215
- transaction = self.complete_transaction(click_trans_id, is_successful)
216
-
217
- return {
218
- 'click_trans_id': click_trans_id,
219
- 'merchant_trans_id': merchant_trans_id,
220
- 'merchant_prepare_id': transaction.get('id'),
221
- 'error': 0,
222
- 'error_note': "Success"
223
- }
224
-
225
- else:
226
- logger.error(f"Unsupported action: {action}")
227
- raise UnsupportedMethod(f"Unsupported action: {action}")
File without changes
gateways/payme/cards.py DELETED
@@ -1,222 +0,0 @@
1
- """
2
- Payme cards operations.
3
- """
4
- import logging
5
- from typing import Dict, Any
6
-
7
- from paytechuz.core.http import HttpClient
8
- from paytechuz.core.constants import PaymeEndpoints
9
- from paytechuz.core.utils import handle_exceptions
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
- class PaymeCards:
14
- """
15
- Payme cards operations.
16
-
17
- This class provides methods for working with cards in the Payme payment system,
18
- including creating cards, verifying cards, and removing cards.
19
- """
20
-
21
- def __init__(self, http_client: HttpClient, payme_id: str):
22
- """
23
- Initialize the Payme cards component.
24
-
25
- Args:
26
- http_client: HTTP client for making requests
27
- payme_id: Payme merchant ID
28
- """
29
- self.http_client = http_client
30
- self.payme_id = payme_id
31
-
32
- @handle_exceptions
33
- def create(
34
- self,
35
- card_number: str,
36
- expire_date: str,
37
- save: bool = True,
38
- **kwargs
39
- ) -> Dict[str, Any]:
40
- """
41
- Create a new card.
42
-
43
- Args:
44
- card_number: Card number
45
- expire_date: Card expiration date in format "MM/YY"
46
- save: Whether to save the card for future use
47
- **kwargs: Additional parameters
48
- - phone: Customer phone number
49
- - language: Language code (uz, ru, en)
50
-
51
- Returns:
52
- Dict containing card creation response
53
- """
54
- # Extract additional parameters
55
- phone = kwargs.get('phone')
56
- language = kwargs.get('language', 'uz')
57
-
58
- # Prepare request data
59
- data = {
60
- "method": PaymeEndpoints.CARDS_CREATE,
61
- "params": {
62
- "card": {
63
- "number": card_number,
64
- "expire": expire_date
65
- },
66
- "save": save,
67
- "merchant_id": self.payme_id
68
- }
69
- }
70
-
71
- # Add optional parameters
72
- if phone:
73
- data["params"]["phone"] = phone
74
-
75
- # Add language header
76
- headers = {"Accept-Language": language}
77
-
78
- # Make request
79
- response = self.http_client.post(
80
- endpoint="",
81
- json_data=data,
82
- headers=headers
83
- )
84
-
85
- return response
86
-
87
- @handle_exceptions
88
- def verify(
89
- self,
90
- token: str,
91
- code: str,
92
- **kwargs
93
- ) -> Dict[str, Any]:
94
- """
95
- Verify a card with the verification code.
96
-
97
- Args:
98
- token: Card token received from create method
99
- code: Verification code sent to the card owner
100
- **kwargs: Additional parameters
101
- - language: Language code (uz, ru, en)
102
-
103
- Returns:
104
- Dict containing card verification response
105
- """
106
- # Extract additional parameters
107
- language = kwargs.get('language', 'uz')
108
-
109
- # Prepare request data
110
- data = {
111
- "method": PaymeEndpoints.CARDS_VERIFY,
112
- "params": {
113
- "token": token,
114
- "code": code
115
- }
116
- }
117
-
118
- # Add language header
119
- headers = {"Accept-Language": language}
120
-
121
- # Make request
122
- response = self.http_client.post(
123
- endpoint="",
124
- json_data=data,
125
- headers=headers
126
- )
127
-
128
- return response
129
-
130
- @handle_exceptions
131
- def check(self, token: str) -> Dict[str, Any]:
132
- """
133
- Check if a card exists and is active.
134
-
135
- Args:
136
- token: Card token
137
-
138
- Returns:
139
- Dict containing card check response
140
- """
141
- # Prepare request data
142
- data = {
143
- "method": PaymeEndpoints.CARDS_CHECK,
144
- "params": {
145
- "token": token
146
- }
147
- }
148
-
149
- # Make request
150
- response = self.http_client.post(
151
- endpoint="",
152
- json_data=data
153
- )
154
-
155
- return response
156
-
157
- @handle_exceptions
158
- def remove(self, token: str) -> Dict[str, Any]:
159
- """
160
- Remove a card.
161
-
162
- Args:
163
- token: Card token
164
-
165
- Returns:
166
- Dict containing card removal response
167
- """
168
- # Prepare request data
169
- data = {
170
- "method": PaymeEndpoints.CARDS_REMOVE,
171
- "params": {
172
- "token": token
173
- }
174
- }
175
-
176
- # Make request
177
- response = self.http_client.post(
178
- endpoint="",
179
- json_data=data
180
- )
181
-
182
- return response
183
-
184
- @handle_exceptions
185
- def get_verify_code(
186
- self,
187
- token: str,
188
- **kwargs
189
- ) -> Dict[str, Any]:
190
- """
191
- Get a new verification code for a card.
192
-
193
- Args:
194
- token: Card token
195
- **kwargs: Additional parameters
196
- - language: Language code (uz, ru, en)
197
-
198
- Returns:
199
- Dict containing verification code response
200
- """
201
- # Extract additional parameters
202
- language = kwargs.get('language', 'uz')
203
-
204
- # Prepare request data
205
- data = {
206
- "method": PaymeEndpoints.CARDS_GET_VERIFY_CODE,
207
- "params": {
208
- "token": token
209
- }
210
- }
211
-
212
- # Add language header
213
- headers = {"Accept-Language": language}
214
-
215
- # Make request
216
- response = self.http_client.post(
217
- endpoint="",
218
- json_data=data,
219
- headers=headers
220
- )
221
-
222
- return response