paytechuz 0.2.8b0__py3-none-any.whl → 0.2.10__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,24 +125,16 @@ 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
- # Set state based on current state and reason
136
137
  if self.state == self.INITIATING or reason_code == 3:
137
- # If transaction is in INITIATING state or reason is 3 (execution error),
138
- # set state to CANCELLED_DURING_INIT (-1)
139
138
  self.state = self.CANCELLED_DURING_INIT
140
139
  else:
141
140
  # Otherwise, set state to CANCELLED (-2)
@@ -155,3 +154,26 @@ class PaymentTransaction(Base):
155
154
  db.refresh(self)
156
155
 
157
156
  return self
157
+
158
+
159
+ def run_migrations(engine: Any) -> None:
160
+ """
161
+ Run database migrations for PayTechUZ FastAPI integration.
162
+
163
+ This function creates all necessary tables in the database for the
164
+ PayTechUZ payment system. Call this function when setting up your FastAPI
165
+ application to ensure all required database tables are created.
166
+
167
+ Example:
168
+ ```python
169
+ from sqlalchemy import create_engine
170
+ from paytechuz.integrations.fastapi.models import run_migrations
171
+
172
+ engine = create_engine("sqlite:///./payments.db")
173
+ run_migrations(engine)
174
+ ```
175
+
176
+ Args:
177
+ engine: SQLAlchemy engine instance
178
+ """
179
+ Base.metadata.create_all(bind=engine)
@@ -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,7 +361,8 @@ class PaymeWebhookHandler:
362
361
  PaymentTransaction.transaction_id != transaction_id
363
362
  ).all()
364
363
 
365
- # Filter out transactions in final states (SUCCESSFULLY, CANCELLED, CANCELLED_DURING_INIT)
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
368
  if t.state not in [
@@ -373,9 +373,11 @@ class PaymeWebhookHandler:
373
373
  ]
374
374
 
375
375
  if non_final_transactions:
376
- # 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
377
378
  raise InvalidAccount(
378
- 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")
379
381
  )
380
382
 
381
383
  # Check for existing transaction with the same transaction_id
@@ -390,7 +392,9 @@ class PaymeWebhookHandler:
390
392
 
391
393
  # For existing transactions, use the original time from extra_data
392
394
  # This ensures the same response is returned for repeated calls
393
- 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
+ )
394
398
 
395
399
  return {
396
400
  'transaction': transaction.transaction_id,
@@ -407,7 +411,9 @@ class PaymeWebhookHandler:
407
411
  state=PaymentTransaction.INITIATING,
408
412
  extra_data={
409
413
  'account_field': self.account_field,
410
- 'account_value': params.get('account', {}).get(self.account_field),
414
+ 'account_value': (
415
+ params.get('account', {}).get(self.account_field)
416
+ ),
411
417
  'create_time': params.get('time'),
412
418
  'raw_params': params
413
419
  }
@@ -420,7 +426,8 @@ class PaymeWebhookHandler:
420
426
  # Call the event method
421
427
  self.transaction_created(params, transaction, account)
422
428
 
423
- # 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
424
431
  create_time = params.get('time')
425
432
 
426
433
  return {
@@ -455,7 +462,9 @@ class PaymeWebhookHandler:
455
462
  return {
456
463
  'transaction': transaction.transaction_id,
457
464
  'state': transaction.state,
458
- '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,
459
468
  }
460
469
 
461
470
  def _check_transaction(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -479,18 +488,28 @@ class PaymeWebhookHandler:
479
488
  self.check_transaction(params, transaction)
480
489
 
481
490
  # Use the original time from extra_data for consistency
482
- 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
+ )
483
494
 
484
495
  return {
485
496
  'transaction': transaction.transaction_id,
486
497
  'state': transaction.state,
487
498
  'create_time': create_time,
488
- 'perform_time': int(transaction.performed_at.timestamp() * 1000) if transaction.performed_at else 0,
489
- '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
+ ),
490
507
  'reason': transaction.reason,
491
508
  }
492
509
 
493
- def _cancel_response(self, transaction: PaymentTransaction) -> Dict[str, Any]:
510
+ def _cancel_response(
511
+ self, transaction: PaymentTransaction
512
+ ) -> Dict[str, Any]:
494
513
  """
495
514
  Helper method to generate cancel transaction response.
496
515
 
@@ -515,7 +534,8 @@ class PaymeWebhookHandler:
515
534
  return {
516
535
  'transaction': transaction.transaction_id,
517
536
  'state': transaction.state,
518
- '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),
519
539
  'reason': reason,
520
540
  }
521
541
 
@@ -538,15 +558,22 @@ class PaymeWebhookHandler:
538
558
  )
539
559
 
540
560
  # Check if transaction is already cancelled
541
- if transaction.state in [PaymentTransaction.CANCELLED, PaymentTransaction.CANCELLED_DURING_INIT]:
542
- # 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
543
568
  if 'reason' in params:
544
569
  reason = params.get('reason')
545
570
 
546
- # If reason is not provided, use default reason from PaymeCancelReason
571
+ # If reason is not provided, use default reason
572
+ # from PaymeCancelReason
547
573
  if reason is None:
548
574
  from paytechuz.core.constants import PaymeCancelReason
549
- reason = PaymeCancelReason.REASON_FUND_RETURNED # Default reason 5
575
+ # Default reason 5
576
+ reason = PaymeCancelReason.REASON_FUND_RETURNED
550
577
 
551
578
  # Convert reason to int if it's a string
552
579
  if isinstance(reason, str) and reason.isdigit():
@@ -573,7 +600,8 @@ class PaymeWebhookHandler:
573
600
  # Ensure the reason is stored in extra_data
574
601
  extra_data = transaction.extra_data or {}
575
602
  if 'cancel_reason' not in extra_data:
576
- 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
577
605
  transaction.extra_data = extra_data
578
606
  self.db.commit()
579
607
  self.db.refresh(transaction)
@@ -620,9 +648,18 @@ class PaymeWebhookHandler:
620
648
  self.account_field: transaction.account_id
621
649
  },
622
650
  'state': transaction.state,
623
- 'create_time': transaction.extra_data.get('create_time', int(transaction.created_at.timestamp() * 1000)),
624
- 'perform_time': int(transaction.performed_at.timestamp() * 1000) if transaction.performed_at else 0,
625
- '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
+ ),
626
663
  'reason': transaction.reason,
627
664
  })
628
665
 
@@ -633,7 +670,9 @@ class PaymeWebhookHandler:
633
670
 
634
671
  # Event methods that can be overridden by subclasses
635
672
 
636
- 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:
637
676
  """
638
677
  Called before checking if a transaction can be performed.
639
678
 
@@ -643,7 +682,9 @@ class PaymeWebhookHandler:
643
682
  """
644
683
  pass
645
684
 
646
- 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:
647
688
  """
648
689
  Called when a transaction already exists.
649
690
 
@@ -653,7 +694,12 @@ class PaymeWebhookHandler:
653
694
  """
654
695
  pass
655
696
 
656
- 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:
657
703
  """
658
704
  Called when a transaction is created.
659
705
 
@@ -664,7 +710,11 @@ class PaymeWebhookHandler:
664
710
  """
665
711
  pass
666
712
 
667
- 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:
668
718
  """
669
719
  Called when a payment is successful.
670
720
 
@@ -674,7 +724,11 @@ class PaymeWebhookHandler:
674
724
  """
675
725
  pass
676
726
 
677
- 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:
678
732
  """
679
733
  Called when checking a transaction.
680
734
 
@@ -684,7 +738,11 @@ class PaymeWebhookHandler:
684
738
  """
685
739
  pass
686
740
 
687
- 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:
688
746
  """
689
747
  Called when a payment is cancelled.
690
748
 
@@ -694,7 +752,11 @@ class PaymeWebhookHandler:
694
752
  """
695
753
  pass
696
754
 
697
- 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:
698
760
  """
699
761
  Called when getting a statement.
700
762
 
@@ -723,7 +785,9 @@ class ClickWebhookHandler:
723
785
  print(f"Payment successful: {transaction.transaction_id}")
724
786
 
725
787
  # Update your order status
726
- 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())
727
791
  order.status = 'paid'
728
792
  db.commit()
729
793
  ```
@@ -792,7 +856,8 @@ class ClickWebhookHandler:
792
856
 
793
857
  # Validate amount
794
858
  try:
795
- self._validate_amount(amount, float(getattr(account, 'amount', 0)))
859
+ expected = float(getattr(account, 'amount', 0))
860
+ self._validate_amount(amount, expected)
796
861
  except Exception as e:
797
862
  logger.error(f"Invalid amount: {e}")
798
863
  return {
@@ -890,7 +955,8 @@ class ClickWebhookHandler:
890
955
  self.successfully_payment(params, transaction)
891
956
  else:
892
957
  # Mark transaction as cancelled
893
- 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)
894
960
 
895
961
  # Call the event method
896
962
  self.cancelled_payment(params, transaction)
@@ -941,10 +1007,15 @@ class ClickWebhookHandler:
941
1007
  )
942
1008
 
943
1009
  # Create string to sign
944
- to_sign = f"{params.get('click_trans_id')}{params.get('service_id')}"
945
- to_sign += f"{self.secret_key}{params.get('merchant_trans_id')}"
946
- to_sign += f"{params.get('amount')}{params.get('action')}"
947
- 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
+ )
948
1019
 
949
1020
  # Generate signature
950
1021
  signature = hashlib.md5(to_sign.encode('utf-8')).hexdigest()
@@ -963,7 +1034,11 @@ class ClickWebhookHandler:
963
1034
  if isinstance(merchant_trans_id, str) and merchant_trans_id.isdigit():
964
1035
  merchant_trans_id = int(merchant_trans_id)
965
1036
 
966
- 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
+ )
967
1042
  if not account:
968
1043
  raise HTTPException(
969
1044
  status_code=status.HTTP_404_NOT_FOUND,
@@ -972,25 +1047,35 @@ class ClickWebhookHandler:
972
1047
 
973
1048
  return account
974
1049
 
975
- 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:
976
1053
  """
977
1054
  Validate payment amount.
978
1055
  """
979
1056
  # Add commission if needed
980
1057
  if self.commission_percent > 0:
981
- expected_amount = expected_amount * (1 + self.commission_percent / 100)
1058
+ commission_factor = 1 + (self.commission_percent / 100)
1059
+ expected_amount = expected_amount * commission_factor
982
1060
  expected_amount = round(expected_amount, 2)
983
1061
 
984
1062
  # Allow small difference due to floating point precision
985
1063
  if abs(received_amount - expected_amount) > 0.01:
986
1064
  raise HTTPException(
987
1065
  status_code=status.HTTP_400_BAD_REQUEST,
988
- 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
+ )
989
1070
  )
990
1071
 
991
1072
  # Event methods that can be overridden by subclasses
992
1073
 
993
- 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:
994
1079
  """
995
1080
  Called when a transaction already exists.
996
1081
 
@@ -1000,7 +1085,12 @@ class ClickWebhookHandler:
1000
1085
  """
1001
1086
  pass
1002
1087
 
1003
- 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:
1004
1094
  """
1005
1095
  Called when a transaction is created.
1006
1096
 
@@ -1011,7 +1101,11 @@ class ClickWebhookHandler:
1011
1101
  """
1012
1102
  pass
1013
1103
 
1014
- 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:
1015
1109
  """
1016
1110
  Called when a payment is successful.
1017
1111
 
@@ -1021,7 +1115,11 @@ class ClickWebhookHandler:
1021
1115
  """
1022
1116
  pass
1023
1117
 
1024
- 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:
1025
1123
  """
1026
1124
  Called when a payment is cancelled.
1027
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,318 @@
1
+ Metadata-Version: 2.4
2
+ Name: paytechuz
3
+ Version: 0.2.10
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 datetime import datetime, timezone
189
+
190
+ from sqlalchemy.orm import sessionmaker
191
+ from sqlalchemy.ext.declarative import declarative_base
192
+ from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime
193
+
194
+ from paytechuz.integrations.fastapi import Base as PaymentsBase
195
+ from paytechuz.integrations.fastapi.models import run_migrations
196
+
197
+
198
+ # Create database engine
199
+ SQLALCHEMY_DATABASE_URL = "sqlite:///./payments.db"
200
+ engine = create_engine(SQLALCHEMY_DATABASE_URL)
201
+
202
+ # Create base declarative class
203
+ Base = declarative_base()
204
+
205
+ # Create Order model
206
+ class Order(Base):
207
+ __tablename__ = "orders"
208
+
209
+ id = Column(Integer, primary_key=True, index=True)
210
+ product_name = Column(String, index=True)
211
+ amount = Column(Float)
212
+ status = Column(String, default="pending")
213
+ created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
214
+
215
+ # Create payment tables using run_migrations
216
+ run_migrations(engine)
217
+
218
+ # Create Order table
219
+ Base.metadata.create_all(bind=engine)
220
+
221
+ # Create session
222
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
223
+ ```
224
+
225
+ 2. Create webhook handlers:
226
+
227
+ ```python
228
+ from fastapi import FastAPI, Request, Depends
229
+
230
+ from sqlalchemy.orm import Session
231
+
232
+ from paytechuz.integrations.fastapi import PaymeWebhookHandler, ClickWebhookHandler
233
+
234
+
235
+ app = FastAPI()
236
+
237
+ # Dependency to get the database session
238
+ def get_db():
239
+ db = SessionLocal()
240
+ try:
241
+ yield db
242
+ finally:
243
+ db.close()
244
+
245
+ class CustomPaymeWebhookHandler(PaymeWebhookHandler):
246
+ def successfully_payment(self, params, transaction):
247
+ # Handle successful payment
248
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
249
+ order.status = "paid"
250
+ self.db.commit()
251
+
252
+ def cancelled_payment(self, params, transaction):
253
+ # Handle cancelled payment
254
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
255
+ order.status = "cancelled"
256
+ self.db.commit()
257
+
258
+ class CustomClickWebhookHandler(ClickWebhookHandler):
259
+ def successfully_payment(self, params, transaction):
260
+ # Handle successful payment
261
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
262
+ order.status = "paid"
263
+ self.db.commit()
264
+
265
+ def cancelled_payment(self, params, transaction):
266
+ # Handle cancelled payment
267
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
268
+ order.status = "cancelled"
269
+ self.db.commit()
270
+
271
+ @app.post("/payments/payme/webhook")
272
+ async def payme_webhook(request: Request, db: Session = Depends(get_db)):
273
+ handler = CustomPaymeWebhookHandler(
274
+ db=db,
275
+ payme_id="your_merchant_id",
276
+ payme_key="your_merchant_key",
277
+ account_model=Order,
278
+ account_field='id',
279
+ amount_field='amount'
280
+ )
281
+ return await handler.handle_webhook(request)
282
+
283
+ @app.post("/payments/click/webhook")
284
+ async def click_webhook(request: Request, db: Session = Depends(get_db)):
285
+ handler = CustomClickWebhookHandler(
286
+ db=db,
287
+ service_id="your_service_id",
288
+ merchant_id="your_merchant_id",
289
+ secret_key="your_secret_key",
290
+ account_model=Order
291
+ )
292
+ return await handler.handle_webhook(request)
293
+ ```
294
+
295
+ ## Documentation
296
+
297
+ Detailed documentation is available in multiple languages:
298
+
299
+ - 📖 [English Documentation](src/docs/en/index.md)
300
+ - 📖 [O'zbek tilidagi hujjatlar](src/docs/index.md)
301
+
302
+ ### Framework-Specific Documentation
303
+
304
+ - [Django Integration Guide](src/docs/en/django_integration.md) | [Django integratsiyasi bo'yicha qo'llanma](src/docs/django_integration.md)
305
+ - [FastAPI Integration Guide](src/docs/en/fastapi_integration.md) | [FastAPI integratsiyasi bo'yicha qo'llanma](src/docs/fastapi_integration.md)
306
+
307
+ ## Supported Payment Systems
308
+
309
+ - **Payme** - [Official Website](https://payme.uz)
310
+ - **Click** - [Official Website](https://click.uz)
311
+
312
+ ## Contributing
313
+
314
+ Contributions are welcome! Please feel free to submit a Pull Request.
315
+
316
+ ## License
317
+
318
+ 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=QW1-iHQ9IiXWvk6-8fo3vLqFZwejY07-Vavfyd5NRzA,4923
31
- paytechuz/integrations/fastapi/routes.py,sha256=hE1ApHZ4aOVNtZRcTup57vcXmxBmHumsc9a3V8BzO4w,36816
32
- paytechuz/integrations/fastapi/schemas.py,sha256=CkNohj22mQQje8Pu_IkTQwUPAoYHNOKXlGjqaRX_SGQ,3784
33
- paytechuz-0.2.8b0.dist-info/METADATA,sha256=zRSCBTPio_z7yA7iZG7LtdlfFuAo3Y_kghr-3cBWMj4,4392
34
- paytechuz-0.2.8b0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
- paytechuz-0.2.8b0.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
36
- paytechuz-0.2.8b0.dist-info/RECORD,,
30
+ paytechuz/integrations/fastapi/models.py,sha256=9IqrsndIVuIDwDbijZ89biJxEWQASXRBfWVShxgerAc,5113
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.10.dist-info/METADATA,sha256=eQP9iaSd-TgqTNeJkB3ECKFYTsqZpG_dFqwLpkWCrsw,9414
34
+ paytechuz-0.2.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ paytechuz-0.2.10.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
36
+ paytechuz-0.2.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,170 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: paytechuz
3
- Version: 0.2.8b0
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)