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.

gateways/payme/webhook.py DELETED
@@ -1,379 +0,0 @@
1
- """
2
- Payme webhook handler.
3
- """
4
- import base64
5
- import binascii
6
- import logging
7
- from typing import Dict, Any, Optional, Callable
8
-
9
- from paytechuz.core.base import BaseWebhookHandler
10
- from paytechuz.core.constants import TransactionState, PaymeCancelReason
11
- from paytechuz.core.exceptions import (
12
- PermissionDenied,
13
- MethodNotFound,
14
- TransactionNotFound,
15
- AccountNotFound,
16
- InternalServiceError,
17
- TransactionCancelled
18
- )
19
- from paytechuz.core.utils import handle_exceptions
20
-
21
- logger = logging.getLogger(__name__)
22
-
23
- class PaymeWebhookHandler(BaseWebhookHandler):
24
- """
25
- Payme webhook handler.
26
-
27
- This class handles webhook requests from the Payme payment system,
28
- including transaction creation, confirmation, and cancellation.
29
- """
30
-
31
- def __init__(
32
- self,
33
- merchant_key: str,
34
- find_transaction_func: Callable[[str], Dict[str, Any]],
35
- find_account_func: Callable[[Dict[str, Any]], Dict[str, Any]],
36
- create_transaction_func: Callable[[Dict[str, Any]], Dict[str, Any]],
37
- perform_transaction_func: Callable[[str], bool],
38
- cancel_transaction_func: Callable[[str, int], bool],
39
- get_statement_func: Optional[Callable[[int, int], list]] = None,
40
- check_perform_func: Optional[Callable[[Dict[str, Any]], bool]] = None,
41
- set_fiscal_data_func: Optional[
42
- Callable[[str, Dict[str, Any]], bool]
43
- ] = None
44
- ):
45
- """
46
- Initialize the Payme webhook handler.
47
-
48
- Args:
49
- merchant_key: Payme merchant key for authentication
50
- find_transaction_func: Function to find a transaction by ID
51
- find_account_func: Function to find an account by parameters
52
- create_transaction_func: Function to create a transaction
53
- perform_transaction_func: Function to perform a transaction
54
- cancel_transaction_func: Function to cancel a transaction
55
- get_statement_func: Function to get transaction statement
56
- check_perform_func: Function to check transaction can be performed
57
- set_fiscal_data_func: Function to set fiscal data for a transaction
58
- """
59
- self.merchant_key = merchant_key
60
- self.find_transaction = find_transaction_func
61
- self.find_account = find_account_func
62
- self.create_transaction = create_transaction_func
63
- self.perform_transaction = perform_transaction_func
64
- self.cancel_transaction = cancel_transaction_func
65
- self.get_statement = get_statement_func
66
- self.check_perform = check_perform_func
67
- self.set_fiscal_data = set_fiscal_data_func
68
-
69
- def _check_auth(self, auth_header: Optional[str]) -> None:
70
- """
71
- Check authentication header.
72
-
73
- Args:
74
- auth_header: Authentication header
75
-
76
- Raises:
77
- PermissionDenied: If authentication fails
78
- """
79
- if not auth_header:
80
- raise PermissionDenied("Missing authentication credentials")
81
-
82
- try:
83
- auth_parts = auth_header.split()
84
- if len(auth_parts) != 2 or auth_parts[0].lower() != 'basic':
85
- raise PermissionDenied("Invalid authentication format")
86
-
87
- auth_decoded = base64.b64decode(auth_parts[1]).decode('utf-8')
88
- _, password = auth_decoded.split(':')
89
-
90
- if password != self.merchant_key:
91
- raise PermissionDenied("Invalid merchant key")
92
- except (binascii.Error, UnicodeDecodeError, ValueError) as e:
93
- logger.error(f"Authentication error: {e}")
94
- raise PermissionDenied("Authentication error")
95
-
96
- @handle_exceptions
97
- def handle_webhook(
98
- self, data: Dict[str, Any], auth_header: Optional[str] = None
99
- ) -> Dict[str, Any]:
100
- """
101
- Handle webhook data from Payme.
102
-
103
- Args:
104
- data: The webhook data received from Payme
105
- auth_header: Authentication header
106
-
107
- Returns:
108
- Dict containing the response to be sent back to Payme
109
-
110
- Raises:
111
- PermissionDenied: If authentication fails
112
- MethodNotFound: If the requested method is not supported
113
- """
114
- # Check authentication
115
- self._check_auth(auth_header)
116
-
117
- # Extract method and params
118
- try:
119
- method = data.get('method')
120
- params = data.get('params', {})
121
- request_id = data.get('id', 0)
122
- except (KeyError, TypeError) as e:
123
- logger.error(f"Invalid webhook data: {e}")
124
- raise InternalServiceError("Invalid webhook data")
125
-
126
- # Map methods to handler functions
127
- method_handlers = {
128
- 'CheckPerformTransaction': self._handle_check_perform,
129
- 'CreateTransaction': self._handle_create_transaction,
130
- 'PerformTransaction': self._handle_perform_transaction,
131
- 'CheckTransaction': self._handle_check_transaction,
132
- 'CancelTransaction': self._handle_cancel_transaction,
133
- 'GetStatement': self._handle_get_statement,
134
- 'SetFiscalData': self._handle_set_fiscal_data,
135
- }
136
-
137
- # Call the appropriate handler
138
- if method in method_handlers:
139
- result = method_handlers[method](params)
140
- return {
141
- 'jsonrpc': '2.0',
142
- 'id': request_id,
143
- 'result': result
144
- }
145
-
146
- logger.warning(f"Method not found: {method}")
147
- raise MethodNotFound(f"Method not supported: {method}")
148
-
149
- def _handle_check_perform(self, params: Dict[str, Any]) -> Dict[str, Any]:
150
- """
151
- Handle CheckPerformTransaction method.
152
-
153
- Args:
154
- params: Method parameters
155
-
156
- Returns:
157
- Dict containing the response
158
- """
159
- if not self.check_perform:
160
- # Default implementation if no custom function is provided
161
- account = self.find_account(params.get('account', {}))
162
- if not account:
163
- raise AccountNotFound("Account not found")
164
-
165
- return {'allow': True}
166
-
167
- # Call custom function
168
- result = self.check_perform(params)
169
- return {'allow': result}
170
-
171
- def _handle_create_transaction(
172
- self, params: Dict[str, Any]
173
- ) -> Dict[str, Any]:
174
- """
175
- Handle CreateTransaction method.
176
-
177
- Args:
178
- params: Method parameters
179
-
180
- Returns:
181
- Dict containing the response
182
- """
183
- transaction_id = params.get('id')
184
-
185
- # Check if transaction already exists
186
- try:
187
- existing_transaction = self.find_transaction(transaction_id)
188
-
189
- # If transaction exists, return its details
190
- return {
191
- 'transaction': existing_transaction['id'],
192
- 'state': existing_transaction['state'],
193
- 'create_time': existing_transaction['create_time'],
194
- }
195
- except TransactionNotFound:
196
- # Transaction doesn't exist, create a new one
197
- pass
198
-
199
- # Find account
200
- account = self.find_account(params.get('account', {}))
201
- if not account:
202
- raise AccountNotFound("Account not found")
203
-
204
- # Create transaction
205
- transaction = self.create_transaction({
206
- 'id': transaction_id,
207
- 'account': account,
208
- 'amount': params.get('amount'),
209
- 'time': params.get('time'),
210
- })
211
-
212
- return {
213
- 'transaction': transaction['id'],
214
- 'state': transaction['state'],
215
- 'create_time': transaction['create_time'],
216
- }
217
-
218
- def _handle_perform_transaction(
219
- self, params: Dict[str, Any]
220
- ) -> Dict[str, Any]:
221
- """
222
- Handle PerformTransaction method.
223
-
224
- Args:
225
- params: Method parameters
226
-
227
- Returns:
228
- Dict containing the response
229
- """
230
- transaction_id = params.get('id')
231
-
232
- # Find transaction
233
- transaction = self.find_transaction(transaction_id)
234
-
235
- # Perform transaction
236
- self.perform_transaction(transaction_id)
237
-
238
- return {
239
- 'transaction': transaction['id'],
240
- 'state': transaction['state'],
241
- 'perform_time': transaction.get('perform_time', 0),
242
- }
243
-
244
- def _handle_check_transaction(
245
- self, params: Dict[str, Any]
246
- ) -> Dict[str, Any]:
247
- """
248
- Handle CheckTransaction method.
249
-
250
- Args:
251
- params: Method parameters
252
-
253
- Returns:
254
- Dict containing the response
255
- """
256
- transaction_id = params.get('id')
257
-
258
- # Find transaction
259
- transaction = self.find_transaction(transaction_id)
260
-
261
- return {
262
- 'transaction': transaction['id'],
263
- 'state': transaction['state'],
264
- 'create_time': transaction['create_time'],
265
- 'perform_time': transaction.get('perform_time', 0),
266
- 'cancel_time': transaction.get('cancel_time', 0),
267
- 'reason': transaction.get('reason'),
268
- }
269
-
270
- def _cancel_response(self, transaction: Dict[str, Any]) -> Dict[str, Any]:
271
- """
272
- Helper method to generate cancel transaction response.
273
-
274
- Args:
275
- transaction: Transaction data
276
-
277
- Returns:
278
- Dict containing the response
279
- """
280
- return {
281
- 'transaction': transaction['id'],
282
- 'state': transaction['state'],
283
- 'cancel_time': transaction.get('cancel_time', 0),
284
- }
285
-
286
- def _handle_cancel_transaction(
287
- self, params: Dict[str, Any]
288
- ) -> Dict[str, Any]:
289
- """
290
- Handle CancelTransaction method.
291
-
292
- Args:
293
- params: Method parameters
294
-
295
- Returns:
296
- Dict containing the response
297
- """
298
- transaction_id = params.get('id')
299
- reason = params.get(
300
- 'reason', PaymeCancelReason.REASON_MERCHANT_DECISION
301
- )
302
-
303
- # Find transaction
304
- transaction = self.find_transaction(transaction_id)
305
-
306
- # Check if transaction is already cancelled
307
- canceled_states = [
308
- TransactionState.CANCELED.value,
309
- TransactionState.CANCELED_DURING_INIT.value
310
- ]
311
- if transaction.get('state') in canceled_states:
312
- # If transaction is already cancelled, return the existing data
313
- return self._cancel_response(transaction)
314
-
315
- # Check if transaction can be cancelled based on its current state
316
- if transaction.get('state') == TransactionState.SUCCESSFULLY.value:
317
- # Transaction was successfully performed, can be cancelled
318
- pass
319
- elif transaction.get('state') == TransactionState.INITIATING.value:
320
- # Transaction is in initiating state, can be cancelled
321
- pass
322
- else:
323
- # If transaction is in another state, it cannot be cancelled
324
- raise TransactionCancelled(
325
- f"Transaction {transaction_id} cannot be cancelled"
326
- )
327
-
328
- # Cancel transaction
329
- self.cancel_transaction(transaction_id, reason)
330
-
331
- # Get updated transaction
332
- updated_transaction = self.find_transaction(transaction_id)
333
-
334
- # Return cancel response
335
- return self._cancel_response(updated_transaction)
336
-
337
- def _handle_get_statement(self, params: Dict[str, Any]) -> Dict[str, Any]:
338
- """
339
- Handle GetStatement method.
340
-
341
- Args:
342
- params: Method parameters
343
-
344
- Returns:
345
- Dict containing the response
346
- """
347
- if not self.get_statement:
348
- raise MethodNotFound("GetStatement method not implemented")
349
-
350
- from_date = params.get('from')
351
- to_date = params.get('to')
352
-
353
- # Get statement
354
- transactions = self.get_statement(from_date, to_date)
355
-
356
- return {'transactions': transactions}
357
-
358
- def _handle_set_fiscal_data(
359
- self, params: Dict[str, Any]
360
- ) -> Dict[str, Any]:
361
- """
362
- Handle SetFiscalData method.
363
-
364
- Args:
365
- params: Method parameters
366
-
367
- Returns:
368
- Dict containing the response
369
- """
370
- if not self.set_fiscal_data:
371
- raise MethodNotFound("SetFiscalData method not implemented")
372
-
373
- transaction_id = params.get('id')
374
- fiscal_data = params.get('fiscal_data', {})
375
-
376
- # Set fiscal data
377
- success = self.set_fiscal_data(transaction_id, fiscal_data)
378
-
379
- return {'success': success}
integrations/__init__.py DELETED
File without changes
@@ -1,4 +0,0 @@
1
- """
2
- Django integration for PayTechUZ.
3
- """
4
- default_app_config = 'paytechuz.integrations.django.apps.PaytechuzConfig'
@@ -1,78 +0,0 @@
1
- """
2
- Django admin configuration for PayTechUZ.
3
- """
4
- from django.contrib import admin
5
- from django.utils.html import format_html
6
-
7
- from .models import PaymentTransaction
8
-
9
- @admin.register(PaymentTransaction)
10
- class PaymentTransactionAdmin(admin.ModelAdmin):
11
- """
12
- Admin configuration for PaymentTransaction model.
13
- """
14
- list_display = (
15
- 'id',
16
- 'gateway',
17
- 'transaction_id',
18
- 'account_id',
19
- 'amount',
20
- 'state_display',
21
- 'created_at',
22
- 'updated_at',
23
- )
24
- list_filter = ('gateway', 'state', 'created_at')
25
- search_fields = ('transaction_id', 'account_id')
26
- readonly_fields = (
27
- 'gateway',
28
- 'transaction_id',
29
- 'account_id',
30
- 'amount',
31
- 'state',
32
- 'extra_data',
33
- 'created_at',
34
- 'updated_at',
35
- 'performed_at',
36
- 'cancelled_at',
37
- )
38
- fieldsets = (
39
- ('Transaction Information', {
40
- 'fields': (
41
- 'gateway',
42
- 'transaction_id',
43
- 'account_id',
44
- 'amount',
45
- 'state',
46
- )
47
- }),
48
- ('Timestamps', {
49
- 'fields': (
50
- 'created_at',
51
- 'updated_at',
52
- 'performed_at',
53
- 'cancelled_at',
54
- )
55
- }),
56
- ('Additional Data', {
57
- 'fields': ('extra_data',),
58
- 'classes': ('collapse',),
59
- }),
60
- )
61
-
62
- def state_display(self, obj):
63
- """
64
- Display the state with a colored badge.
65
- """
66
- if obj.state == PaymentTransaction.CREATED:
67
- return format_html('<span style="background-color: #f8f9fa; color: #212529; padding: 3px 8px; border-radius: 4px;">Created</span>')
68
- elif obj.state == PaymentTransaction.INITIATING:
69
- return format_html('<span style="background-color: #fff3cd; color: #856404; padding: 3px 8px; border-radius: 4px;">Initiating</span>')
70
- elif obj.state == PaymentTransaction.SUCCESSFULLY:
71
- return format_html('<span style="background-color: #d4edda; color: #155724; padding: 3px 8px; border-radius: 4px;">Successfully</span>')
72
- elif obj.state == PaymentTransaction.CANCELLED:
73
- return format_html('<span style="background-color: #f8d7da; color: #721c24; padding: 3px 8px; border-radius: 4px;">Cancelled</span>')
74
- elif obj.state == PaymentTransaction.CANCELLED_DURING_INIT:
75
- return format_html('<span style="background-color: #f8d7da; color: #721c24; padding: 3px 8px; border-radius: 4px;">Cancelled (Init)</span>')
76
- return obj.get_state_display()
77
-
78
- state_display.short_description = 'State'
@@ -1,21 +0,0 @@
1
- """
2
- Django app configuration for PayTechUZ.
3
- """
4
- from django.apps import AppConfig
5
-
6
- class PaytechuzConfig(AppConfig):
7
- """
8
- Django app configuration for PayTechUZ.
9
- """
10
- name = 'paytechuz.integrations.django'
11
- verbose_name = 'PayTechUZ'
12
-
13
- def ready(self):
14
- """
15
- Initialize the app.
16
- """
17
- # Import signals
18
- try:
19
- import paytechuz.integrations.django.signals # noqa
20
- except ImportError:
21
- pass
@@ -1,51 +0,0 @@
1
- """
2
- Initial migration for PaymentTransaction model.
3
- """
4
- from django.db import migrations, models
5
-
6
-
7
- class Migration(migrations.Migration):
8
- """
9
- Initial migration for PaymentTransaction model.
10
- """
11
- initial = True
12
-
13
- dependencies = []
14
-
15
- operations = [
16
- migrations.CreateModel(
17
- name='PaymentTransaction',
18
- fields=[
19
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
- ('gateway', models.CharField(choices=[('payme', 'Payme'), ('click', 'Click')], max_length=10)),
21
- ('transaction_id', models.CharField(max_length=255)),
22
- ('account_id', models.CharField(max_length=255)),
23
- ('amount', models.DecimalField(decimal_places=2, max_digits=15)),
24
- ('state', models.IntegerField(choices=[(0, 'Created'), (1, 'Initiating'), (2, 'Successfully'), (-2, 'Cancelled after successful performed'), (-1, 'Cancelled during initiation')], default=0)),
25
- ('reason', models.IntegerField(blank=True, null=True, help_text='Reason for cancellation')),
26
- ('extra_data', models.JSONField(blank=True, default=dict)),
27
- ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
28
- ('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
29
- ('performed_at', models.DateTimeField(blank=True, db_index=True, null=True)),
30
- ('cancelled_at', models.DateTimeField(blank=True, db_index=True, null=True)),
31
- ],
32
- options={
33
- 'verbose_name': 'Payment Transaction',
34
- 'verbose_name_plural': 'Payment Transactions',
35
- 'db_table': 'payments',
36
- 'ordering': ['-created_at'],
37
- },
38
- ),
39
- migrations.AddIndex(
40
- model_name='paymenttransaction',
41
- index=models.Index(fields=['account_id'], name='payments_account_d4c2a8_idx'),
42
- ),
43
- migrations.AddIndex(
44
- model_name='paymenttransaction',
45
- index=models.Index(fields=['state'], name='payments_state_e0ceac_idx'),
46
- ),
47
- migrations.AlterUniqueTogether(
48
- name='paymenttransaction',
49
- unique_together={('gateway', 'transaction_id')},
50
- ),
51
- ]
@@ -1,3 +0,0 @@
1
- """
2
- Django migrations for PayTechUZ.
3
- """