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

@@ -9,6 +9,7 @@ from sqlalchemy.ext.declarative import declarative_base
9
9
 
10
10
  Base = declarative_base()
11
11
 
12
+
12
13
  class PaymentTransaction(Base):
13
14
  """
14
15
  Payment transaction model for storing payment information.
@@ -35,7 +36,11 @@ class PaymentTransaction(Base):
35
36
  reason = Column(Integer, nullable=True) # Reason for cancellation
36
37
  extra_data = Column(JSON, default={})
37
38
  created_at = Column(DateTime, default=datetime.utcnow, index=True)
38
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, index=True)
39
+ updated_at = Column(
40
+ DateTime,
41
+ default=datetime.utcnow,
42
+ onupdate=datetime.utcnow, index=True
43
+ )
39
44
  performed_at = Column(DateTime, nullable=True, index=True)
40
45
  cancelled_at = Column(DateTime, nullable=True, index=True)
41
46
 
@@ -107,7 +112,9 @@ class PaymentTransaction(Base):
107
112
 
108
113
  return self
109
114
 
110
- def mark_as_cancelled(self, db, reason: Optional[str] = None) -> "PaymentTransaction":
115
+ def mark_as_cancelled(
116
+ self, db, reason: Optional[str] = None
117
+ ) -> "PaymentTransaction":
111
118
  """
112
119
  Mark the transaction as cancelled.
113
120
 
@@ -118,23 +125,21 @@ class PaymentTransaction(Base):
118
125
  Returns:
119
126
  PaymentTransaction instance
120
127
  """
121
- # If reason is not provided, use default reason from PaymeCancelReason
122
128
  if reason is None:
123
- # Import here to avoid circular imports
124
- from paytechuz.core.constants import PaymeCancelReason
125
- reason_code = PaymeCancelReason.REASON_FUND_RETURNED # Default reason 5
129
+ reason_code = 5 # REASON_FUND_RETURNED
126
130
  else:
127
- # Convert reason to int if it's a string
128
131
  if isinstance(reason, str) and reason.isdigit():
129
132
  reason_code = int(reason)
130
133
  else:
131
134
  reason_code = reason
132
135
 
133
- # Only update state if not already cancelled
134
136
  if self.state not in [self.CANCELLED, self.CANCELLED_DURING_INIT]:
135
- # Always set state to CANCELLED (-2) for Payme API compatibility
136
- # regardless of the current state
137
- self.state = self.CANCELLED
137
+ if self.state == self.INITIATING or reason_code == 3:
138
+ self.state = self.CANCELLED_DURING_INIT
139
+ else:
140
+ # Otherwise, set state to CANCELLED (-2)
141
+ self.state = self.CANCELLED
142
+
138
143
  self.cancelled_at = datetime.utcnow()
139
144
 
140
145
  # Store the reason directly in the reason column
@@ -7,11 +7,10 @@ import json
7
7
  import logging
8
8
  from datetime import datetime
9
9
  from decimal import Decimal
10
- from typing import Dict, Any, Optional, Callable
10
+ from typing import Dict, Any, Optional
11
11
 
12
12
  from fastapi import (
13
13
  APIRouter,
14
- Depends,
15
14
  HTTPException,
16
15
  Request,
17
16
  Response,
@@ -19,6 +18,7 @@ from fastapi import (
19
18
  )
20
19
  from sqlalchemy.orm import Session
21
20
 
21
+ # pylint: disable=E0401,E0611
22
22
  from paytechuz.core.exceptions import (
23
23
  PermissionDenied,
24
24
  InvalidAmount,
@@ -185,13 +185,12 @@ class PaymeWebhookHandler:
185
185
  'jsonrpc': '2.0',
186
186
  'id': request_id if 'request_id' in locals() else 0,
187
187
  'error': {
188
- # Code for account not found, in the range -31099 to -31050
189
188
  'code': -31050,
190
189
  'message': str(e)
191
190
  }
192
191
  }),
193
192
  media_type="application/json",
194
- status_code=200 # Return 200 status code for all errors
193
+ status_code=200
195
194
  )
196
195
 
197
196
  except InvalidAmount as e:
@@ -200,12 +199,12 @@ class PaymeWebhookHandler:
200
199
  'jsonrpc': '2.0',
201
200
  'id': request_id if 'request_id' in locals() else 0,
202
201
  'error': {
203
- 'code': -31001, # Code for invalid amount
202
+ 'code': -31001,
204
203
  'message': str(e)
205
204
  }
206
205
  }),
207
206
  media_type="application/json",
208
- status_code=200 # Return 200 status code for all errors
207
+ status_code=200
209
208
  )
210
209
 
211
210
  except InvalidAccount as e:
@@ -214,13 +213,12 @@ class PaymeWebhookHandler:
214
213
  'jsonrpc': '2.0',
215
214
  'id': request_id if 'request_id' in locals() else 0,
216
215
  'error': {
217
- # Code for invalid account, in the range -31099 to -31050
218
216
  'code': -31050,
219
217
  'message': str(e)
220
218
  }
221
219
  }),
222
220
  media_type="application/json",
223
- status_code=200 # Return 200 status code for all errors
221
+ status_code=200
224
222
  )
225
223
 
226
224
  except TransactionNotFound as e:
@@ -234,7 +232,7 @@ class PaymeWebhookHandler:
234
232
  }
235
233
  }),
236
234
  media_type="application/json",
237
- status_code=200 # Return 200 status code for all errors
235
+ status_code=200
238
236
  )
239
237
 
240
238
  except Exception as e:
@@ -249,7 +247,7 @@ class PaymeWebhookHandler:
249
247
  }
250
248
  }),
251
249
  media_type="application/json",
252
- status_code=200 # Return 200 status code for all errors
250
+ status_code=200
253
251
  )
254
252
 
255
253
  def _check_auth(self, auth_header: Optional[str]) -> None:
@@ -270,7 +268,6 @@ class PaymeWebhookHandler:
270
268
  if password != self.payme_key:
271
269
  raise PermissionDenied("Invalid merchant key")
272
270
  except PermissionDenied:
273
- # Re-raise permission denied exceptions
274
271
  raise
275
272
  except Exception as e:
276
273
  logger.error(f"Authentication error: {e}")
@@ -284,13 +281,12 @@ class PaymeWebhookHandler:
284
281
  if not account_value:
285
282
  raise AccountNotFound("Account not found in parameters")
286
283
 
287
- # Handle special case for 'order_id' field
288
284
  lookup_field = 'id' if self.account_field == 'order_id' else (
289
285
  self.account_field
290
286
  )
291
287
 
292
- # Try to convert account_value to int if it's a string and lookup_field is 'id'
293
- if (lookup_field == 'id' and isinstance(account_value, str) and
288
+ if (lookup_field == 'id' and
289
+ isinstance(account_value, str) and
294
290
  account_value.isdigit()):
295
291
  account_value = int(account_value)
296
292
 
@@ -324,12 +320,15 @@ class PaymeWebhookHandler:
324
320
  # If one_time_payment is disabled, amount must be positive
325
321
  if not self.one_time_payment and received_amount <= 0:
326
322
  raise InvalidAmount(
327
- f"Invalid amount. Amount must be positive, received: {received_amount}"
323
+ (f"Invalid amount. Amount must be positive, "
324
+ f"received: {received_amount}")
328
325
  )
329
326
 
330
327
  return True
331
328
 
332
- def _check_perform_transaction(self, params: Dict[str, Any]) -> Dict[str, Any]:
329
+ def _check_perform_transaction(
330
+ self, params: Dict[str, Any]
331
+ ) -> Dict[str, Any]:
333
332
  """
334
333
  Handle CheckPerformTransaction method.
335
334
  """
@@ -351,8 +350,8 @@ class PaymeWebhookHandler:
351
350
 
352
351
  self._validate_amount(account, amount)
353
352
 
354
- # Check if there's already a transaction for this account with a different transaction_id
355
- # Only check if one_time_payment is enabled
353
+ # Check if there's already a transaction for this account with a
354
+ # different transaction_id. Only check if one_time_payment is enabled
356
355
  if self.one_time_payment:
357
356
  # Check for existing transactions in non-final states
358
357
  existing_transactions = self.db.query(PaymentTransaction).filter(
@@ -362,16 +361,23 @@ class PaymeWebhookHandler:
362
361
  PaymentTransaction.transaction_id != transaction_id
363
362
  ).all()
364
363
 
365
- # Filter out transactions in final states (SUCCESSFULLY, CANCELLED)
364
+ # Filter out transactions in final states
365
+ # (SUCCESSFULLY, CANCELLED, CANCELLED_DURING_INIT)
366
366
  non_final_transactions = [
367
367
  t for t in existing_transactions
368
- if t.state not in [PaymentTransaction.SUCCESSFULLY, PaymentTransaction.CANCELLED]
368
+ if t.state not in [
369
+ PaymentTransaction.SUCCESSFULLY,
370
+ PaymentTransaction.CANCELLED,
371
+ PaymentTransaction.CANCELLED_DURING_INIT
372
+ ]
369
373
  ]
370
374
 
371
375
  if non_final_transactions:
372
- # If there's already a transaction for this account with a different transaction_id in a non-final state, raise an error
376
+ # If there's already a transaction for this account with a
377
+ # different transaction_id in a non-final state, raise an error
373
378
  raise InvalidAccount(
374
- f"Account with {self.account_field}={account.id} already has a pending transaction"
379
+ (f"Account with {self.account_field}={account.id} "
380
+ f"already has a pending transaction")
375
381
  )
376
382
 
377
383
  # Check for existing transaction with the same transaction_id
@@ -386,7 +392,9 @@ class PaymeWebhookHandler:
386
392
 
387
393
  # For existing transactions, use the original time from extra_data
388
394
  # This ensures the same response is returned for repeated calls
389
- create_time = transaction.extra_data.get('create_time', params.get('time'))
395
+ create_time = transaction.extra_data.get(
396
+ 'create_time', params.get('time')
397
+ )
390
398
 
391
399
  return {
392
400
  'transaction': transaction.transaction_id,
@@ -403,7 +411,9 @@ class PaymeWebhookHandler:
403
411
  state=PaymentTransaction.INITIATING,
404
412
  extra_data={
405
413
  'account_field': self.account_field,
406
- 'account_value': params.get('account', {}).get(self.account_field),
414
+ 'account_value': (
415
+ params.get('account', {}).get(self.account_field)
416
+ ),
407
417
  'create_time': params.get('time'),
408
418
  'raw_params': params
409
419
  }
@@ -416,7 +426,8 @@ class PaymeWebhookHandler:
416
426
  # Call the event method
417
427
  self.transaction_created(params, transaction, account)
418
428
 
419
- # Use the time from the request params instead of transaction.created_at
429
+ # Use the time from the request params
430
+ # instead of transaction.created_at
420
431
  create_time = params.get('time')
421
432
 
422
433
  return {
@@ -451,7 +462,9 @@ class PaymeWebhookHandler:
451
462
  return {
452
463
  'transaction': transaction.transaction_id,
453
464
  'state': transaction.state,
454
- 'perform_time': int(transaction.performed_at.timestamp() * 1000) if transaction.performed_at else 0,
465
+ 'perform_time': int(
466
+ transaction.performed_at.timestamp() * 1000
467
+ ) if transaction.performed_at else 0,
455
468
  }
456
469
 
457
470
  def _check_transaction(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -475,18 +488,28 @@ class PaymeWebhookHandler:
475
488
  self.check_transaction(params, transaction)
476
489
 
477
490
  # Use the original time from extra_data for consistency
478
- create_time = transaction.extra_data.get('create_time', int(transaction.created_at.timestamp() * 1000))
491
+ create_time = transaction.extra_data.get(
492
+ 'create_time', int(transaction.created_at.timestamp() * 1000)
493
+ )
479
494
 
480
495
  return {
481
496
  'transaction': transaction.transaction_id,
482
497
  'state': transaction.state,
483
498
  'create_time': create_time,
484
- 'perform_time': int(transaction.performed_at.timestamp() * 1000) if transaction.performed_at else 0,
485
- 'cancel_time': int(transaction.cancelled_at.timestamp() * 1000) if transaction.cancelled_at else 0,
499
+ 'perform_time': (
500
+ int(transaction.performed_at.timestamp() * 1000)
501
+ if transaction.performed_at else 0
502
+ ),
503
+ 'cancel_time': (
504
+ int(transaction.cancelled_at.timestamp() * 1000)
505
+ if transaction.cancelled_at else 0
506
+ ),
486
507
  'reason': transaction.reason,
487
508
  }
488
509
 
489
- def _cancel_response(self, transaction: PaymentTransaction) -> Dict[str, Any]:
510
+ def _cancel_response(
511
+ self, transaction: PaymentTransaction
512
+ ) -> Dict[str, Any]:
490
513
  """
491
514
  Helper method to generate cancel transaction response.
492
515
 
@@ -511,7 +534,8 @@ class PaymeWebhookHandler:
511
534
  return {
512
535
  'transaction': transaction.transaction_id,
513
536
  'state': transaction.state,
514
- 'cancel_time': int(transaction.cancelled_at.timestamp() * 1000) if transaction.cancelled_at else 0,
537
+ 'cancel_time': (int(transaction.cancelled_at.timestamp() * 1000)
538
+ if transaction.cancelled_at else 0),
515
539
  'reason': reason,
516
540
  }
517
541
 
@@ -534,15 +558,22 @@ class PaymeWebhookHandler:
534
558
  )
535
559
 
536
560
  # Check if transaction is already cancelled
537
- if transaction.state == PaymentTransaction.CANCELLED:
538
- # If transaction is already cancelled, update the reason if provided
561
+ cancelled_states = [
562
+ PaymentTransaction.CANCELLED,
563
+ PaymentTransaction.CANCELLED_DURING_INIT
564
+ ]
565
+ if transaction.state in cancelled_states:
566
+ # If transaction is already cancelled, update the reason
567
+ # if provided
539
568
  if 'reason' in params:
540
569
  reason = params.get('reason')
541
570
 
542
- # If reason is not provided, use default reason from PaymeCancelReason
571
+ # If reason is not provided, use default reason
572
+ # from PaymeCancelReason
543
573
  if reason is None:
544
574
  from paytechuz.core.constants import PaymeCancelReason
545
- reason = PaymeCancelReason.REASON_FUND_RETURNED # Default reason 5
575
+ # Default reason 5
576
+ reason = PaymeCancelReason.REASON_FUND_RETURNED
546
577
 
547
578
  # Convert reason to int if it's a string
548
579
  if isinstance(reason, str) and reason.isdigit():
@@ -569,7 +600,8 @@ class PaymeWebhookHandler:
569
600
  # Ensure the reason is stored in extra_data
570
601
  extra_data = transaction.extra_data or {}
571
602
  if 'cancel_reason' not in extra_data:
572
- extra_data['cancel_reason'] = reason if reason is not None else 5 # Default reason 5
603
+ # Default reason 5 if none provided
604
+ extra_data['cancel_reason'] = reason if reason is not None else 5
573
605
  transaction.extra_data = extra_data
574
606
  self.db.commit()
575
607
  self.db.refresh(transaction)
@@ -616,9 +648,18 @@ class PaymeWebhookHandler:
616
648
  self.account_field: transaction.account_id
617
649
  },
618
650
  'state': transaction.state,
619
- 'create_time': transaction.extra_data.get('create_time', int(transaction.created_at.timestamp() * 1000)),
620
- 'perform_time': int(transaction.performed_at.timestamp() * 1000) if transaction.performed_at else 0,
621
- 'cancel_time': int(transaction.cancelled_at.timestamp() * 1000) if transaction.cancelled_at else 0,
651
+ 'create_time': transaction.extra_data.get(
652
+ 'create_time',
653
+ int(transaction.created_at.timestamp() * 1000)
654
+ ),
655
+ 'perform_time': (
656
+ int(transaction.performed_at.timestamp() * 1000)
657
+ if transaction.performed_at else 0
658
+ ),
659
+ 'cancel_time': (
660
+ int(transaction.cancelled_at.timestamp() * 1000)
661
+ if transaction.cancelled_at else 0
662
+ ),
622
663
  'reason': transaction.reason,
623
664
  })
624
665
 
@@ -629,7 +670,9 @@ class PaymeWebhookHandler:
629
670
 
630
671
  # Event methods that can be overridden by subclasses
631
672
 
632
- def before_check_perform_transaction(self, params: Dict[str, Any], account: Any) -> None:
673
+ def before_check_perform_transaction(
674
+ self, params: Dict[str, Any], account: Any
675
+ ) -> None:
633
676
  """
634
677
  Called before checking if a transaction can be performed.
635
678
 
@@ -639,7 +682,9 @@ class PaymeWebhookHandler:
639
682
  """
640
683
  pass
641
684
 
642
- def transaction_already_exists(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
685
+ def transaction_already_exists(
686
+ self, params: Dict[str, Any], transaction: PaymentTransaction
687
+ ) -> None:
643
688
  """
644
689
  Called when a transaction already exists.
645
690
 
@@ -649,7 +694,12 @@ class PaymeWebhookHandler:
649
694
  """
650
695
  pass
651
696
 
652
- def transaction_created(self, params: Dict[str, Any], transaction: PaymentTransaction, account: Any) -> None:
697
+ def transaction_created(
698
+ self,
699
+ params: Dict[str, Any],
700
+ transaction: PaymentTransaction,
701
+ account: Any
702
+ ) -> None:
653
703
  """
654
704
  Called when a transaction is created.
655
705
 
@@ -660,7 +710,11 @@ class PaymeWebhookHandler:
660
710
  """
661
711
  pass
662
712
 
663
- def successfully_payment(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
713
+ def successfully_payment(
714
+ self,
715
+ params: Dict[str, Any],
716
+ transaction: PaymentTransaction
717
+ ) -> None:
664
718
  """
665
719
  Called when a payment is successful.
666
720
 
@@ -670,7 +724,11 @@ class PaymeWebhookHandler:
670
724
  """
671
725
  pass
672
726
 
673
- def check_transaction(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
727
+ def check_transaction(
728
+ self,
729
+ params: Dict[str, Any],
730
+ transaction: PaymentTransaction
731
+ ) -> None:
674
732
  """
675
733
  Called when checking a transaction.
676
734
 
@@ -680,7 +738,11 @@ class PaymeWebhookHandler:
680
738
  """
681
739
  pass
682
740
 
683
- def cancelled_payment(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
741
+ def cancelled_payment(
742
+ self,
743
+ params: Dict[str, Any],
744
+ transaction: PaymentTransaction
745
+ ) -> None:
684
746
  """
685
747
  Called when a payment is cancelled.
686
748
 
@@ -690,7 +752,11 @@ class PaymeWebhookHandler:
690
752
  """
691
753
  pass
692
754
 
693
- def get_statement(self, params: Dict[str, Any], transactions: list) -> None:
755
+ def get_statement(
756
+ self,
757
+ params: Dict[str, Any],
758
+ transactions: list
759
+ ) -> None:
694
760
  """
695
761
  Called when getting a statement.
696
762
 
@@ -719,7 +785,9 @@ class ClickWebhookHandler:
719
785
  print(f"Payment successful: {transaction.transaction_id}")
720
786
 
721
787
  # Update your order status
722
- order = db.query(Order).filter(Order.id == transaction.account_id).first()
788
+ order = (db.query(Order)
789
+ .filter(Order.id == transaction.account_id)
790
+ .first())
723
791
  order.status = 'paid'
724
792
  db.commit()
725
793
  ```
@@ -788,7 +856,8 @@ class ClickWebhookHandler:
788
856
 
789
857
  # Validate amount
790
858
  try:
791
- self._validate_amount(amount, float(getattr(account, 'amount', 0)))
859
+ expected = float(getattr(account, 'amount', 0))
860
+ self._validate_amount(amount, expected)
792
861
  except Exception as e:
793
862
  logger.error(f"Invalid amount: {e}")
794
863
  return {
@@ -886,7 +955,8 @@ class ClickWebhookHandler:
886
955
  self.successfully_payment(params, transaction)
887
956
  else:
888
957
  # Mark transaction as cancelled
889
- transaction.mark_as_cancelled(self.db, reason=f"Error code: {error}")
958
+ error_reason = f"Error code: {error}"
959
+ transaction.mark_as_cancelled(self.db, reason=error_reason)
890
960
 
891
961
  # Call the event method
892
962
  self.cancelled_payment(params, transaction)
@@ -937,10 +1007,15 @@ class ClickWebhookHandler:
937
1007
  )
938
1008
 
939
1009
  # Create string to sign
940
- to_sign = f"{params.get('click_trans_id')}{params.get('service_id')}"
941
- to_sign += f"{self.secret_key}{params.get('merchant_trans_id')}"
942
- to_sign += f"{params.get('amount')}{params.get('action')}"
943
- to_sign += f"{sign_time}"
1010
+ to_sign = (
1011
+ f"{params.get('click_trans_id')}"
1012
+ f"{params.get('service_id')}"
1013
+ f"{self.secret_key}"
1014
+ f"{params.get('merchant_trans_id')}"
1015
+ f"{params.get('amount')}"
1016
+ f"{params.get('action')}"
1017
+ f"{sign_time}"
1018
+ )
944
1019
 
945
1020
  # Generate signature
946
1021
  signature = hashlib.md5(to_sign.encode('utf-8')).hexdigest()
@@ -959,7 +1034,11 @@ class ClickWebhookHandler:
959
1034
  if isinstance(merchant_trans_id, str) and merchant_trans_id.isdigit():
960
1035
  merchant_trans_id = int(merchant_trans_id)
961
1036
 
962
- account = self.db.query(self.account_model).filter_by(id=merchant_trans_id).first()
1037
+ account = (
1038
+ self.db.query(self.account_model)
1039
+ .filter_by(id=merchant_trans_id)
1040
+ .first()
1041
+ )
963
1042
  if not account:
964
1043
  raise HTTPException(
965
1044
  status_code=status.HTTP_404_NOT_FOUND,
@@ -968,25 +1047,35 @@ class ClickWebhookHandler:
968
1047
 
969
1048
  return account
970
1049
 
971
- def _validate_amount(self, received_amount: float, expected_amount: float) -> None:
1050
+ def _validate_amount(
1051
+ self, received_amount: float, expected_amount: float
1052
+ ) -> None:
972
1053
  """
973
1054
  Validate payment amount.
974
1055
  """
975
1056
  # Add commission if needed
976
1057
  if self.commission_percent > 0:
977
- expected_amount = expected_amount * (1 + self.commission_percent / 100)
1058
+ commission_factor = 1 + (self.commission_percent / 100)
1059
+ expected_amount = expected_amount * commission_factor
978
1060
  expected_amount = round(expected_amount, 2)
979
1061
 
980
1062
  # Allow small difference due to floating point precision
981
1063
  if abs(received_amount - expected_amount) > 0.01:
982
1064
  raise HTTPException(
983
1065
  status_code=status.HTTP_400_BAD_REQUEST,
984
- detail=f"Incorrect amount. Expected: {expected_amount}, received: {received_amount}"
1066
+ detail=(
1067
+ f"Incorrect amount. Expected: {expected_amount}, "
1068
+ f"received: {received_amount}"
1069
+ )
985
1070
  )
986
1071
 
987
1072
  # Event methods that can be overridden by subclasses
988
1073
 
989
- def transaction_already_exists(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
1074
+ def transaction_already_exists(
1075
+ self,
1076
+ params: Dict[str, Any],
1077
+ transaction: PaymentTransaction
1078
+ ) -> None:
990
1079
  """
991
1080
  Called when a transaction already exists.
992
1081
 
@@ -996,7 +1085,12 @@ class ClickWebhookHandler:
996
1085
  """
997
1086
  pass
998
1087
 
999
- def transaction_created(self, params: Dict[str, Any], transaction: PaymentTransaction, account: Any) -> None:
1088
+ def transaction_created(
1089
+ self,
1090
+ params: Dict[str, Any],
1091
+ transaction: PaymentTransaction,
1092
+ account: Any
1093
+ ) -> None:
1000
1094
  """
1001
1095
  Called when a transaction is created.
1002
1096
 
@@ -1007,7 +1101,11 @@ class ClickWebhookHandler:
1007
1101
  """
1008
1102
  pass
1009
1103
 
1010
- def successfully_payment(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
1104
+ def successfully_payment(
1105
+ self,
1106
+ params: Dict[str, Any],
1107
+ transaction: PaymentTransaction
1108
+ ) -> None:
1011
1109
  """
1012
1110
  Called when a payment is successful.
1013
1111
 
@@ -1017,7 +1115,11 @@ class ClickWebhookHandler:
1017
1115
  """
1018
1116
  pass
1019
1117
 
1020
- def cancelled_payment(self, params: Dict[str, Any], transaction: PaymentTransaction) -> None:
1118
+ def cancelled_payment(
1119
+ self,
1120
+ params: Dict[str, Any],
1121
+ transaction: PaymentTransaction
1122
+ ) -> None:
1021
1123
  """
1022
1124
  Called when a payment is cancelled.
1023
1125
 
@@ -6,12 +6,15 @@ from typing import Dict, Any, Optional, List
6
6
 
7
7
  from pydantic import BaseModel, Field
8
8
 
9
+
9
10
  class PaymentTransactionBase(BaseModel):
10
11
  """
11
12
  Base schema for payment transaction.
12
13
  """
13
14
  gateway: str = Field(..., description="Payment gateway (payme or click)")
14
- transaction_id: str = Field(..., description="Transaction ID from the payment system")
15
+ transaction_id: str = Field(
16
+ ..., description="Transaction ID from the payment system"
17
+ )
15
18
  account_id: str = Field(..., description="Account or order ID")
16
19
  amount: float = Field(..., description="Payment amount")
17
20
  state: int = Field(0, description="Transaction state")
@@ -21,18 +24,27 @@ class PaymentTransactionCreate(PaymentTransactionBase):
21
24
  """
22
25
  Schema for creating a payment transaction.
23
26
  """
24
- extra_data: Optional[Dict[str, Any]] = Field(None, description="Additional data for the transaction")
27
+ extra_data: Optional[Dict[str, Any]] = Field(
28
+ None, description="Additional data for the transaction"
29
+ )
30
+
25
31
 
26
32
  class PaymentTransaction(PaymentTransactionBase):
27
33
  """
28
34
  Schema for payment transaction.
29
35
  """
30
36
  id: int = Field(..., description="Transaction ID")
31
- extra_data: Dict[str, Any] = Field({}, description="Additional data for the transaction")
37
+ extra_data: Dict[str, Any] = Field(
38
+ {}, description="Additional data for the transaction"
39
+ )
32
40
  created_at: datetime = Field(..., description="Creation timestamp")
33
41
  updated_at: datetime = Field(..., description="Last update timestamp")
34
- performed_at: Optional[datetime] = Field(None, description="Payment timestamp")
35
- cancelled_at: Optional[datetime] = Field(None, description="Cancellation timestamp")
42
+ performed_at: Optional[datetime] = Field(
43
+ None, description="Payment timestamp"
44
+ )
45
+ cancelled_at: Optional[datetime] = Field(
46
+ None, description="Cancellation timestamp"
47
+ )
36
48
 
37
49
  class Config:
38
50
  """
@@ -45,9 +57,12 @@ class PaymentTransactionList(BaseModel):
45
57
  """
46
58
  Schema for a list of payment transactions.
47
59
  """
48
- transactions: List[PaymentTransaction] = Field(..., description="List of transactions")
60
+ transactions: List[PaymentTransaction] = Field(
61
+ ..., description="List of transactions"
62
+ )
49
63
  total: int = Field(..., description="Total number of transactions")
50
64
 
65
+
51
66
  class PaymeWebhookRequest(BaseModel):
52
67
  """
53
68
  Schema for Payme webhook request.
@@ -65,6 +80,7 @@ class PaymeWebhookResponse(BaseModel):
65
80
  id: int = Field(..., description="Request ID")
66
81
  result: Dict[str, Any] = Field(..., description="Response result")
67
82
 
83
+
68
84
  class PaymeWebhookErrorResponse(BaseModel):
69
85
  """
70
86
  Schema for Payme webhook error response.
@@ -88,12 +104,15 @@ class ClickWebhookRequest(BaseModel):
88
104
  error: Optional[str] = Field(None, description="Error code")
89
105
  error_note: Optional[str] = Field(None, description="Error note")
90
106
 
107
+
91
108
  class ClickWebhookResponse(BaseModel):
92
109
  """
93
110
  Schema for Click webhook response.
94
111
  """
95
112
  click_trans_id: str = Field(..., description="Click transaction ID")
96
113
  merchant_trans_id: str = Field(..., description="Merchant transaction ID")
97
- merchant_prepare_id: Optional[int] = Field(None, description="Merchant prepare ID")
114
+ merchant_prepare_id: Optional[int] = Field(
115
+ None, description="Merchant prepare ID"
116
+ )
98
117
  error: int = Field(0, description="Error code")
99
118
  error_note: str = Field("Success", description="Error note")
@@ -0,0 +1,312 @@
1
+ Metadata-Version: 2.4
2
+ Name: paytechuz
3
+ Version: 0.2.8
4
+ Summary: Unified Python package for Uzbekistan payment gateways
5
+ Home-page: https://github.com/Muhammadali-Akbarov/paytechuz
6
+ Author: Muhammadali Akbarov
7
+ Author-email: muhammadali17abc@gmail.com
8
+ License: MIT
9
+ Requires-Python: >=3.6
10
+ Description-Content-Type: text/markdown
11
+ Dynamic: author
12
+ Dynamic: author-email
13
+ Dynamic: home-page
14
+ Dynamic: requires-python
15
+
16
+ # PayTechUZ
17
+
18
+ [![PyPI version](https://badge.fury.io/py/paytechuz.svg)](https://badge.fury.io/py/paytechuz)
19
+ [![Python Versions](https://img.shields.io/pypi/pyversions/paytechuz.svg)](https://pypi.org/project/paytechuz/)
20
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
21
+
22
+ PayTechUZ is a unified payment library for integrating with popular payment systems in Uzbekistan. It provides a simple and consistent interface for working with Payme and Click payment gateways.
23
+
24
+ ## Features
25
+
26
+ - 🔄 **API**: Consistent interface for multiple payment providers
27
+ - 🛡️ **Secure**: Built-in security features for payment processing
28
+ - 🔌 **Framework Integration**: Native support for Django and FastAPI
29
+ - 🌐 **Webhook Handling**: Easy-to-use webhook handlers for payment notifications
30
+ - 📊 **Transaction Management**: Automatic transaction tracking and management
31
+ - 🧩 **Extensible**: Easy to add new payment providers
32
+ ## Installation
33
+
34
+ ### Basic Installation
35
+
36
+ ```bash
37
+ pip install paytechuz
38
+ ```
39
+
40
+ ### Framework-Specific Installation
41
+
42
+ ```bash
43
+ # For Django
44
+ pip install paytechuz[django]
45
+
46
+ # For FastAPI
47
+ pip install paytechuz[fastapi]
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ### Generate Payment Links
53
+
54
+ ```python
55
+ from paytechuz.gateways.payme import PaymeGateway
56
+ from paytechuz.gateways.click import ClickGateway
57
+
58
+ # Initialize Payme gateway
59
+ payme = PaymeGateway(
60
+ payme_id="your_payme_id",
61
+ payme_key="your_payme_key",
62
+ is_test_mode=True # Set to False in production environment
63
+ )
64
+
65
+ # Initialize Click gateway
66
+ click = ClickGateway(
67
+ service_id="your_service_id",
68
+ merchant_id="your_merchant_id",
69
+ merchant_user_id="your_merchant_user_id",
70
+ secret_key="your_secret_key",
71
+ is_test_mode=True # Set to False in production environment
72
+ )
73
+
74
+ # Generate payment links
75
+ payme_link = payme.create_payment(
76
+ id="order_123",
77
+ amount=150000, # amount in UZS
78
+ return_url="https://example.com/return"
79
+ )
80
+
81
+ click_link = click.create_payment(
82
+ id="order_123",
83
+ amount=150000, # amount in UZS
84
+ description="Test payment",
85
+ return_url="https://example.com/return"
86
+ )
87
+ ```
88
+
89
+ ### Django Integration
90
+
91
+ 1. Create Order model:
92
+
93
+ ```python
94
+ # models.py
95
+ from django.db import models
96
+ from django.utils import timezone
97
+
98
+ class Order(models.Model):
99
+ STATUS_CHOICES = (
100
+ ('pending', 'Pending'),
101
+ ('paid', 'Paid'),
102
+ ('cancelled', 'Cancelled'),
103
+ ('delivered', 'Delivered'),
104
+ )
105
+
106
+ product_name = models.CharField(max_length=255)
107
+ amount = models.DecimalField(max_digits=12, decimal_places=2)
108
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
109
+ created_at = models.DateTimeField(default=timezone.now)
110
+
111
+ def __str__(self):
112
+ return f"{self.id} - {self.product_name} ({self.amount})"
113
+ ```
114
+
115
+ 2. Add to `INSTALLED_APPS` and configure settings:
116
+
117
+ ```python
118
+ # settings.py
119
+ INSTALLED_APPS = [
120
+ # ...
121
+ 'paytechuz.integrations.django',
122
+ ]
123
+
124
+ PAYME_ID = 'your_payme_merchant_id'
125
+ PAYME_KEY = 'your_payme_merchant_key'
126
+ PAYME_ACCOUNT_MODEL = 'your_app.models.Order' # For example: 'orders.models.Order'
127
+ PAYME_ACCOUNT_FIELD = 'id'
128
+ PAYME_AMOUNT_FIELD = 'amount' # Field for storing payment amount
129
+ PAYME_ONE_TIME_PAYMENT = True # Allow only one payment per account
130
+
131
+ CLICK_SERVICE_ID = 'your_click_service_id'
132
+ CLICK_MERCHANT_ID = 'your_click_merchant_id'
133
+ CLICK_SECRET_KEY = 'your_click_secret_key'
134
+ CLICK_ACCOUNT_MODEL = 'your_app.models.Order'
135
+ CLICK_COMMISSION_PERCENT = 0.0
136
+ ```
137
+
138
+ 3. Create webhook handlers:
139
+
140
+ ```python
141
+ # views.py
142
+ from paytechuz.integrations.django.views import BasePaymeWebhookView, BaseClickWebhookView
143
+ from .models import Order
144
+
145
+ class PaymeWebhookView(BasePaymeWebhookView):
146
+ def successfully_payment(self, params, transaction):
147
+ order = Order.objects.get(id=transaction.account_id)
148
+ order.status = 'paid'
149
+ order.save()
150
+
151
+ def cancelled_payment(self, params, transaction):
152
+ order = Order.objects.get(id=transaction.account_id)
153
+ order.status = 'cancelled'
154
+ order.save()
155
+
156
+ class ClickWebhookView(BaseClickWebhookView):
157
+ def successfully_payment(self, params, transaction):
158
+ order = Order.objects.get(id=transaction.account_id)
159
+ order.status = 'paid'
160
+ order.save()
161
+
162
+ def cancelled_payment(self, params, transaction):
163
+ order = Order.objects.get(id=transaction.account_id)
164
+ order.status = 'cancelled'
165
+ order.save()
166
+ ```
167
+
168
+ 4. Add webhook URLs to `urls.py`:
169
+
170
+ ```python
171
+ # urls.py
172
+ from django.urls import path
173
+ from django.views.decorators.csrf import csrf_exempt
174
+ from .views import PaymeWebhookView, ClickWebhookView
175
+
176
+ urlpatterns = [
177
+ # ...
178
+ path('payments/webhook/payme/', csrf_exempt(PaymeWebhookView.as_view()), name='payme_webhook'),
179
+ path('payments/webhook/click/', csrf_exempt(ClickWebhookView.as_view()), name='click_webhook'),
180
+ ]
181
+ ```
182
+
183
+ ### FastAPI Integration
184
+
185
+ 1. Set up database models:
186
+
187
+ ```python
188
+ from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime
189
+ from sqlalchemy.ext.declarative import declarative_base
190
+ from sqlalchemy.orm import sessionmaker
191
+ from paytechuz.integrations.fastapi import Base as PaymentsBase
192
+ from paytechuz.integrations.fastapi.models import run_migrations
193
+ from datetime import datetime, timezone
194
+
195
+ # Create database engine
196
+ SQLALCHEMY_DATABASE_URL = "sqlite:///./payments.db"
197
+ engine = create_engine(SQLALCHEMY_DATABASE_URL)
198
+
199
+ # Create base declarative class
200
+ Base = declarative_base()
201
+
202
+ # Create Order model
203
+ class Order(Base):
204
+ __tablename__ = "orders"
205
+
206
+ id = Column(Integer, primary_key=True, index=True)
207
+ product_name = Column(String, index=True)
208
+ amount = Column(Float)
209
+ status = Column(String, default="pending")
210
+ created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
211
+
212
+ # Create payment tables using run_migrations
213
+ run_migrations(engine)
214
+
215
+ # Create Order table
216
+ Base.metadata.create_all(bind=engine)
217
+
218
+ # Create session
219
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
220
+ ```
221
+
222
+ 2. Create webhook handlers:
223
+
224
+ ```python
225
+ from fastapi import FastAPI, Request, Depends
226
+ from sqlalchemy.orm import Session
227
+ from paytechuz.integrations.fastapi import PaymeWebhookHandler, ClickWebhookHandler
228
+
229
+ app = FastAPI()
230
+
231
+ # Dependency to get the database session
232
+ def get_db():
233
+ db = SessionLocal()
234
+ try:
235
+ yield db
236
+ finally:
237
+ db.close()
238
+
239
+ class CustomPaymeWebhookHandler(PaymeWebhookHandler):
240
+ def successfully_payment(self, params, transaction):
241
+ # Handle successful payment
242
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
243
+ order.status = "paid"
244
+ self.db.commit()
245
+
246
+ def cancelled_payment(self, params, transaction):
247
+ # Handle cancelled payment
248
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
249
+ order.status = "cancelled"
250
+ self.db.commit()
251
+
252
+ class CustomClickWebhookHandler(ClickWebhookHandler):
253
+ def successfully_payment(self, params, transaction):
254
+ # Handle successful payment
255
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
256
+ order.status = "paid"
257
+ self.db.commit()
258
+
259
+ def cancelled_payment(self, params, transaction):
260
+ # Handle cancelled payment
261
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
262
+ order.status = "cancelled"
263
+ self.db.commit()
264
+
265
+ @app.post("/payments/payme/webhook")
266
+ async def payme_webhook(request: Request, db: Session = Depends(get_db)):
267
+ handler = CustomPaymeWebhookHandler(
268
+ db=db,
269
+ payme_id="your_merchant_id",
270
+ payme_key="your_merchant_key",
271
+ account_model=Order,
272
+ account_field='id',
273
+ amount_field='amount'
274
+ )
275
+ return await handler.handle_webhook(request)
276
+
277
+ @app.post("/payments/click/webhook")
278
+ async def click_webhook(request: Request, db: Session = Depends(get_db)):
279
+ handler = CustomClickWebhookHandler(
280
+ db=db,
281
+ service_id="your_service_id",
282
+ merchant_id="your_merchant_id",
283
+ secret_key="your_secret_key",
284
+ account_model=Order
285
+ )
286
+ return await handler.handle_webhook(request)
287
+ ```
288
+
289
+ ## Documentation
290
+
291
+ Detailed documentation is available in multiple languages:
292
+
293
+ - 📖 [English Documentation](src/docs/en/index.md)
294
+ - 📖 [O'zbek tilidagi hujjatlar](src/docs/index.md)
295
+
296
+ ### Framework-Specific Documentation
297
+
298
+ - [Django Integration Guide](src/docs/en/django_integration.md) | [Django integratsiyasi bo'yicha qo'llanma](src/docs/django_integration.md)
299
+ - [FastAPI Integration Guide](src/docs/en/fastapi_integration.md) | [FastAPI integratsiyasi bo'yicha qo'llanma](src/docs/fastapi_integration.md)
300
+
301
+ ## Supported Payment Systems
302
+
303
+ - **Payme** - [Official Website](https://payme.uz)
304
+ - **Click** - [Official Website](https://click.uz)
305
+
306
+ ## Contributing
307
+
308
+ Contributions are welcome! Please feel free to submit a Pull Request.
309
+
310
+ ## License
311
+
312
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -27,10 +27,10 @@ paytechuz/integrations/django/webhooks.py,sha256=cP_Jc3VlyyvyzDbBd2yEVHikw60th1_
27
27
  paytechuz/integrations/django/migrations/0001_initial.py,sha256=SWHIUuwq91crzaxa9v1UK0kay8CxsjUo6t4bqg7j0Gw,1896
28
28
  paytechuz/integrations/django/migrations/__init__.py,sha256=KLQ5NdjOMLDS21-u3b_g08G1MjPMMhG95XI_N8m4FSo,41
29
29
  paytechuz/integrations/fastapi/__init__.py,sha256=DLnhAZQZf2ghu8BuFFfE7FzbNKWQQ2SLG8qxldRuwR4,565
30
- paytechuz/integrations/fastapi/models.py,sha256=eWGUpiKufj47AK8Hld4A91jRDj0ZKQzAf95CyUozmvo,4638
31
- paytechuz/integrations/fastapi/routes.py,sha256=D17QeyY4-aX6tCNmk5h3UiavukvVrE5e6JOFCy4t_n8,36629
32
- paytechuz/integrations/fastapi/schemas.py,sha256=CkNohj22mQQje8Pu_IkTQwUPAoYHNOKXlGjqaRX_SGQ,3784
33
- paytechuz-0.2.7.dist-info/METADATA,sha256=gwH2lLsL2ikMoLcZjJARIh2uHGDk7xFFc31yYYhubo4,4390
34
- paytechuz-0.2.7.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
- paytechuz-0.2.7.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
36
- paytechuz-0.2.7.dist-info/RECORD,,
30
+ paytechuz/integrations/fastapi/models.py,sha256=uayHUDt0nN6hIkhbGrM1NZRXzpCPvaPMPEjowJFUQPI,4425
31
+ paytechuz/integrations/fastapi/routes.py,sha256=X7ejcICe4lFtpsKMXxvyrklqHWQJMhR-AdhcitSiXlE,37647
32
+ paytechuz/integrations/fastapi/schemas.py,sha256=PgRqviJiD4-u3_CIkUOX8R7L8Yqn8L44WLte7968G0E,3887
33
+ paytechuz-0.2.8.dist-info/METADATA,sha256=YKr2kp5NH_VapJ1DCn7H6zVQ_cerzHvXwnYAHqBtIfM,9407
34
+ paytechuz-0.2.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
+ paytechuz-0.2.8.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
36
+ paytechuz-0.2.8.dist-info/RECORD,,
@@ -1,170 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: paytechuz
3
- Version: 0.2.7
4
- Summary: Unified Python package for Uzbekistan payment gateways
5
- Home-page: https://github.com/Muhammadali-Akbarov/paytechuz
6
- Author: Muhammadali Akbarov
7
- Author-email: muhammadali17abc@gmail.com
8
- License: MIT
9
- Requires-Python: >=3.6
10
- Description-Content-Type: text/markdown
11
- Dynamic: author
12
- Dynamic: author-email
13
- Dynamic: home-page
14
- Dynamic: requires-python
15
-
16
- # PayTechUZ
17
-
18
- [![PyPI version](https://badge.fury.io/py/paytechuz.svg)](https://badge.fury.io/py/paytechuz)
19
- [![Python Versions](https://img.shields.io/pypi/pyversions/paytechuz.svg)](https://pypi.org/project/paytechuz/)
20
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
21
- ## Installation
22
-
23
- ### Basic Installation
24
-
25
- ```bash
26
- pip install paytechuz
27
- ```
28
-
29
- ### Framework-Specific Installation
30
-
31
- ```bash
32
- # For Django
33
- pip install paytechuz[django]
34
-
35
- # For FastAPI
36
- pip install paytechuz[fastapi]
37
- ```
38
-
39
- ## Quick Start
40
-
41
- ### Generate Payment Links
42
-
43
- ```python
44
- from paytechuz import create_gateway, PaymentGateway
45
-
46
- # Initialize gateways
47
- payme = create_gateway(PaymentGateway.PAYME.value,
48
- payme_id="your_payme_id",
49
- payme_key="your_payme_key",
50
- is_test_mode=True
51
- )
52
-
53
- click = create_gateway(PaymentGateway.CLICK.value,
54
- service_id="your_service_id",
55
- merchant_id="your_merchant_id",
56
- merchant_user_id="your_merchant_user_id",
57
- secret_key="your_secret_key",
58
- is_test_mode=True
59
- )
60
-
61
- # Generate payment links
62
- payme_link = payme.create_payment(
63
- id="order_123",
64
- amount=150000, # amount in UZS
65
- return_url="https://example.com/return"
66
- )
67
-
68
- click_link = click.create_payment(
69
- id="order_123",
70
- amount=150000, # amount in UZS
71
- description="Test payment",
72
- return_url="https://example.com/return"
73
- )
74
- ```
75
-
76
- ### Django Integration
77
-
78
- 1. Add to `INSTALLED_APPS`:
79
-
80
- ```python
81
- # settings.py
82
- INSTALLED_APPS = [
83
- # ...
84
- 'paytechuz.integrations.django',
85
- ]
86
-
87
- PAYME_ID = 'your_payme_merchant_id'
88
- PAYME_KEY = 'your_payme_merchant_key'
89
- PAYME_ACCOUNT_MODEL = 'your_app.models.YourModel' # For example: 'orders.models.Order'
90
- PAYME_ACCOUNT_FIELD = 'id'
91
- PAYME_AMOUNT_FIELD = 'amount' # Field for storing payment amount
92
- PAYME_ONE_TIME_PAYMENT = True # Allow only one payment per account
93
-
94
- CLICK_SERVICE_ID = 'your_click_service_id'
95
- CLICK_MERCHANT_ID = 'your_click_merchant_id'
96
- CLICK_SECRET_KEY = 'your_click_secret_key'
97
- CLICK_ACCOUNT_MODEL = 'your_app.models.YourModel'
98
- CLICK_COMMISSION_PERCENT = 0.0
99
- ```
100
-
101
- 2. Create webhook handlers:
102
-
103
- ```python
104
- # views.py
105
- from paytechuz.integrations.django.views import BasePaymeWebhookView
106
- from .models import Order
107
-
108
- class PaymeWebhookView(BasePaymeWebhookView):
109
- def successfully_payment(self, params, transaction):
110
- order = Order.objects.get(id=transaction.account_id)
111
- order.status = 'paid'
112
- order.save()
113
-
114
- def cancelled_payment(self, params, transaction):
115
- order = Order.objects.get(id=transaction.account_id)
116
- order.status = 'cancelled'
117
- order.save()
118
- ```
119
-
120
- 3. Add webhook URLs to `urls.py`:
121
-
122
- ```python
123
- # urls.py
124
- from django.urls import path
125
- from .views import PaymeWebhookView
126
-
127
- urlpatterns = [
128
- # ...
129
- path('payments/webhook/payme/', PaymeWebhookView.as_view(), name='payme_webhook'),
130
- ]
131
- ```
132
-
133
- ### FastAPI Integration
134
-
135
- 1. Create webhook handler:
136
-
137
- ```python
138
- from fastapi import FastAPI, Request
139
- from paytechuz.integrations.fastapi import PaymeWebhookHandler
140
-
141
- app = FastAPI()
142
-
143
- class CustomPaymeWebhookHandler(PaymeWebhookHandler):
144
- def successfully_payment(self, params, transaction):
145
- # Handle successful payment
146
- order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
147
- order.status = "paid"
148
- self.db.commit()
149
-
150
- def cancelled_payment(self, params, transaction):
151
- # Handle cancelled payment
152
- order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
153
- order.status = "cancelled"
154
- self.db.commit()
155
-
156
- @app.post("/payments/payme/webhook")
157
- async def payme_webhook(request: Request):
158
- handler = CustomPaymeWebhookHandler(
159
- payme_id="your_merchant_id",
160
- payme_key="your_merchant_key"
161
- )
162
- return await handler.handle_webhook(request)
163
- ```
164
-
165
- ## Documentation
166
-
167
- Detailed documentation is available in multiple languages:
168
-
169
- - 📖 [English Documentation](docs/en/index.md)
170
- - 📖 [O'zbek tilidagi hujjatlar](docs/uz/index.md)