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

Files changed (40) hide show
  1. {paytechuz/core → core}/base.py +12 -3
  2. gateways/payme/client.py +262 -0
  3. paytechuz-0.1.4.dist-info/METADATA +146 -0
  4. paytechuz-0.1.4.dist-info/RECORD +35 -0
  5. paytechuz-0.1.4.dist-info/top_level.txt +3 -0
  6. paytechuz/__init__.py +0 -76
  7. paytechuz/gateways/payme/client.py +0 -238
  8. paytechuz-0.1.3.dist-info/METADATA +0 -198
  9. paytechuz-0.1.3.dist-info/RECORD +0 -36
  10. paytechuz-0.1.3.dist-info/top_level.txt +0 -1
  11. {paytechuz/core → core}/__init__.py +0 -0
  12. {paytechuz/core → core}/constants.py +0 -0
  13. {paytechuz/core → core}/exceptions.py +0 -0
  14. {paytechuz/core → core}/http.py +0 -0
  15. {paytechuz/core → core}/payme/errors.py +0 -0
  16. {paytechuz/core → core}/utils.py +0 -0
  17. {paytechuz/gateways → gateways}/__init__.py +0 -0
  18. {paytechuz/gateways → gateways}/click/__init__.py +0 -0
  19. {paytechuz/gateways → gateways}/click/client.py +0 -0
  20. {paytechuz/gateways → gateways}/click/merchant.py +0 -0
  21. {paytechuz/gateways → gateways}/click/webhook.py +0 -0
  22. {paytechuz/gateways → gateways}/payme/__init__.py +0 -0
  23. {paytechuz/gateways → gateways}/payme/cards.py +0 -0
  24. {paytechuz/gateways → gateways}/payme/receipts.py +0 -0
  25. {paytechuz/gateways → gateways}/payme/webhook.py +0 -0
  26. {paytechuz/integrations → integrations}/__init__.py +0 -0
  27. {paytechuz/integrations → integrations}/django/__init__.py +0 -0
  28. {paytechuz/integrations → integrations}/django/admin.py +0 -0
  29. {paytechuz/integrations → integrations}/django/apps.py +0 -0
  30. {paytechuz/integrations → integrations}/django/migrations/0001_initial.py +0 -0
  31. {paytechuz/integrations → integrations}/django/migrations/__init__.py +0 -0
  32. {paytechuz/integrations → integrations}/django/models.py +0 -0
  33. {paytechuz/integrations → integrations}/django/signals.py +0 -0
  34. {paytechuz/integrations → integrations}/django/views.py +0 -0
  35. {paytechuz/integrations → integrations}/django/webhooks.py +0 -0
  36. {paytechuz/integrations → integrations}/fastapi/__init__.py +0 -0
  37. {paytechuz/integrations → integrations}/fastapi/models.py +0 -0
  38. {paytechuz/integrations → integrations}/fastapi/routes.py +0 -0
  39. {paytechuz/integrations → integrations}/fastapi/schemas.py +0 -0
  40. {paytechuz-0.1.3.dist-info → paytechuz-0.1.4.dist-info}/WHEEL +0 -0
@@ -4,6 +4,7 @@ Base classes for payment gateways.
4
4
  from abc import ABC, abstractmethod
5
5
  from typing import Dict, Any, Optional, Union
6
6
 
7
+
7
8
  class BasePaymentGateway(ABC):
8
9
  """
9
10
  Base class for all payment gateways.
@@ -23,7 +24,12 @@ class BasePaymentGateway(ABC):
23
24
  self.is_test_mode = is_test_mode
24
25
 
25
26
  @abstractmethod
26
- def create_payment(self, amount: Union[int, float, str], account_id: Union[int, str], **kwargs) -> Dict[str, Any]:
27
+ def create_payment(
28
+ self,
29
+ amount: Union[int, float, str],
30
+ account_id: Union[int, str],
31
+ **kwargs
32
+ ) -> Dict[str, Any]:
27
33
  """
28
34
  Create a payment.
29
35
 
@@ -51,7 +57,11 @@ class BasePaymentGateway(ABC):
51
57
  raise NotImplementedError
52
58
 
53
59
  @abstractmethod
54
- def cancel_payment(self, transaction_id: str, reason: Optional[str] = None) -> Dict[str, Any]:
60
+ def cancel_payment(
61
+ self,
62
+ transaction_id: str,
63
+ reason: Optional[str] = None
64
+ ) -> Dict[str, Any]:
55
65
  """
56
66
  Cancel payment.
57
67
 
@@ -65,7 +75,6 @@ class BasePaymentGateway(ABC):
65
75
  raise NotImplementedError
66
76
 
67
77
 
68
-
69
78
  class BaseWebhookHandler(ABC):
70
79
  """
71
80
  Base class for payment gateway webhook handlers.
@@ -0,0 +1,262 @@
1
+ """
2
+ Payme payment gateway client.
3
+ """
4
+ import logging
5
+ from typing import Dict, Any, Optional, Union
6
+ import base64
7
+
8
+ from paytechuz.core.base import BasePaymentGateway
9
+ from paytechuz.core.http import HttpClient
10
+ from paytechuz.core.constants import PaymeNetworks
11
+ from paytechuz.core.utils import format_amount, handle_exceptions
12
+ from paytechuz.gateways.payme.cards import PaymeCards
13
+ from paytechuz.gateways.payme.receipts import PaymeReceipts
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class PaymeGateway(BasePaymentGateway):
19
+ """
20
+ Payme payment gateway implementation.
21
+
22
+ This class provides methods for interacting with the Payme payment gateway,
23
+ including creating payments, checking payment status, and canceling payments.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ payme_id: str,
29
+ payme_key: Optional[str] = None,
30
+ fallback_id: Optional[str] = None,
31
+ is_test_mode: bool = False
32
+ ):
33
+ """
34
+ Initialize the Payme gateway.
35
+
36
+ Args:
37
+ payme_id: Payme merchant ID
38
+ payme_key: Payme merchant key for authentication
39
+ fallback_id: Fallback merchant ID
40
+ is_test_mode: Whether to use the test environment
41
+ """
42
+ super().__init__(is_test_mode)
43
+ self.payme_id = payme_id
44
+ self.payme_key = payme_key
45
+ self.fallback_id = fallback_id
46
+
47
+ # Set the API URL based on the environment
48
+ url = PaymeNetworks.TEST_NET if is_test_mode else PaymeNetworks.PROD_NET
49
+
50
+ # Initialize HTTP client
51
+ self.http_client = HttpClient(base_url=url)
52
+
53
+ # Initialize components
54
+ self.cards = PaymeCards(http_client=self.http_client, payme_id=payme_id)
55
+ self.receipts = PaymeReceipts(
56
+ http_client=self.http_client,
57
+ payme_id=payme_id,
58
+ payme_key=payme_key
59
+ )
60
+
61
+ def generate_pay_link(
62
+ self,
63
+ id: Union[int, str],
64
+ amount: Union[int, float, str],
65
+ return_url: str,
66
+ account_field_name: str = "order_id"
67
+ ) -> str:
68
+ """
69
+ Generate a payment link for a specific order.
70
+
71
+ Parameters
72
+ ----------
73
+ id : Union[int, str]
74
+ Unique identifier for the account/order.
75
+ amount : Union[int, float, str]
76
+ Payment amount in som.
77
+ return_url : str
78
+ URL to redirect after payment completion.
79
+ account_field_name : str, optional
80
+ Field name for account identifier (default: "order_id").
81
+
82
+ Returns
83
+ -------
84
+ str
85
+ Payme checkout URL with encoded parameters.
86
+
87
+ References
88
+ ----------
89
+ https://developer.help.paycom.uz/initsializatsiya-platezhey/
90
+ """
91
+ # Convert amount to tiyin (1 som = 100 tiyin)
92
+ amount_tiyin = int(float(amount) * 100)
93
+
94
+ # Build parameters
95
+ params = (
96
+ f'm={self.payme_id};'
97
+ f'ac.{account_field_name}={id};'
98
+ f'a={amount_tiyin};'
99
+ f'c={return_url}'
100
+ )
101
+ encoded_params = base64.b64encode(params.encode("utf-8")).decode("utf-8")
102
+
103
+ # Return URL based on environment
104
+ base_url = "https://test.paycom.uz" if self.is_test_mode else "https://checkout.paycom.uz"
105
+ return f"{base_url}/{encoded_params}"
106
+
107
+ async def generate_pay_link_async(
108
+ self,
109
+ id: Union[int, str],
110
+ amount: Union[int, float, str],
111
+ return_url: str,
112
+ account_field_name: str = "order_id"
113
+ ) -> str:
114
+ """
115
+ Async version of generate_pay_link.
116
+
117
+ Parameters
118
+ ----------
119
+ id : Union[int, str]
120
+ Unique identifier for the account/order.
121
+ amount : Union[int, float, str]
122
+ Payment amount in som.
123
+ return_url : str
124
+ URL to redirect after payment completion.
125
+ account_field_name : str, optional
126
+ Field name for account identifier (default: "order_id").
127
+
128
+ Returns
129
+ -------
130
+ str
131
+ Payme checkout URL with encoded parameters.
132
+ """
133
+ return self.generate_pay_link(
134
+ id=id,
135
+ amount=amount,
136
+ return_url=return_url,
137
+ account_field_name=account_field_name
138
+ )
139
+
140
+ @handle_exceptions
141
+ def create_payment(
142
+ self,
143
+ id: Union[int, str],
144
+ amount: Union[int, float, str],
145
+ return_url: str = "",
146
+ account_field_name: str = "order_id"
147
+ ) -> str:
148
+ """
149
+ Create a payment using Payme.
150
+
151
+ Args:
152
+ amount: Payment amount in som
153
+ account_id: Account or order ID
154
+ return_url: Return URL after payment (default: "")
155
+ account_field_name: Field name for account ID (default: "order_id")
156
+
157
+ Returns:
158
+ str: Payme payment URL
159
+ """
160
+ return self.generate_pay_link(
161
+ id=id,
162
+ amount=amount,
163
+ return_url=return_url,
164
+ account_field_name=account_field_name
165
+ )
166
+
167
+ @handle_exceptions
168
+ async def create_payment_async(
169
+ self,
170
+ id: Union[int, str],
171
+ amount: Union[int, float, str],
172
+ return_url: str = "",
173
+ account_field_name: str = "order_id"
174
+ ) -> str:
175
+ """
176
+ Async version of create_payment.
177
+
178
+ Args:
179
+ amount: Payment amount in som
180
+ account_id: Account or order ID
181
+ return_url: Return URL after payment (default: "")
182
+ account_field_name: Field name for account ID (default: "order_id")
183
+
184
+ Returns:
185
+ str: Payme payment URL
186
+ """
187
+ return await self.generate_pay_link_async(
188
+ id=id,
189
+ amount=amount,
190
+ return_url=return_url,
191
+ account_field_name=account_field_name
192
+ )
193
+
194
+ @handle_exceptions
195
+ def check_payment(self, transaction_id: str) -> Dict[str, Any]:
196
+ """
197
+ Check payment status using Payme receipts.
198
+
199
+ Args:
200
+ transaction_id: The receipt ID to check
201
+
202
+ Returns:
203
+ Dict containing payment status and details
204
+ """
205
+ receipt_data = self.receipts.check(receipt_id=transaction_id)
206
+
207
+ # Extract receipt status
208
+ receipt = receipt_data.get('receipt', {})
209
+ status = receipt.get('state')
210
+
211
+ # Map Payme status to our status
212
+ status_mapping = {
213
+ 0: 'created',
214
+ 1: 'waiting',
215
+ 2: 'paid',
216
+ 3: 'cancelled',
217
+ 4: 'refunded'
218
+ }
219
+
220
+ mapped_status = status_mapping.get(status, 'unknown')
221
+
222
+ return {
223
+ 'transaction_id': transaction_id,
224
+ 'status': mapped_status,
225
+ 'amount': receipt.get('amount') / 100, # Convert from tiyin to som
226
+ 'paid_at': receipt.get('pay_time'),
227
+ 'created_at': receipt.get('create_time'),
228
+ 'cancelled_at': receipt.get('cancel_time'),
229
+ 'raw_response': receipt_data
230
+ }
231
+
232
+ @handle_exceptions
233
+ def cancel_payment(
234
+ self,
235
+ transaction_id: str,
236
+ reason: Optional[str] = None
237
+ ) -> Dict[str, Any]:
238
+ """
239
+ Cancel payment using Payme receipts.
240
+
241
+ Args:
242
+ transaction_id: The receipt ID to cancel
243
+ reason: Optional reason for cancellation
244
+
245
+ Returns:
246
+ Dict containing cancellation status and details
247
+ """
248
+ receipt_data = self.receipts.cancel(
249
+ receipt_id=transaction_id,
250
+ reason=reason or "Cancelled by merchant"
251
+ )
252
+
253
+ # Extract receipt status
254
+ receipt = receipt_data.get('receipt', {})
255
+ status = receipt.get('state')
256
+
257
+ return {
258
+ 'transaction_id': transaction_id,
259
+ 'status': 'cancelled' if status == 3 else 'unknown',
260
+ 'cancelled_at': receipt.get('cancel_time'),
261
+ 'raw_response': receipt_data
262
+ }
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: paytechuz
3
+ Version: 0.1.4
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.gateways.payme import PaymeGateway
45
+ from paytechuz.gateways.click import ClickGateway
46
+
47
+ # Initialize gateways
48
+ payme = PaymeGateway()
49
+ click = ClickGateway()
50
+
51
+ # Generate payment links
52
+ payme_link = payme.generate_payment_link(
53
+ id="order_123",
54
+ amount=150000, # amount in UZS
55
+ return_url="https://example.com/return"
56
+ )
57
+
58
+ click_link = click.generate_payment_link(
59
+ id="order_123",
60
+ amount=150000, # amount in UZS
61
+ return_url="https://example.com/return"
62
+ )
63
+ ```
64
+
65
+ ### Django Integration
66
+
67
+ 1. Add to `INSTALLED_APPS`:
68
+
69
+ ```python
70
+ # settings.py
71
+ INSTALLED_APPS = [
72
+ # ...
73
+ 'paytechuz.integrations.django',
74
+ ]
75
+
76
+ PAYME_ID = 'your_payme_merchant_id'
77
+ PAYME_KEY = 'your_payme_merchant_key'
78
+ PAYME_ACCOUNT_MODEL = 'your_app.models.YourModel' # For example: 'orders.models.Order'
79
+ PAYME_ACCOUNT_FIELD = 'id'
80
+ PAYME_AMOUNT_FIELD = 'amount' # Field for storing payment amount
81
+ PAYME_ONE_TIME_PAYMENT = True # Allow only one payment per account
82
+
83
+ CLICK_SERVICE_ID = 'your_click_service_id'
84
+ CLICK_MERCHANT_ID = 'your_click_merchant_id'
85
+ CLICK_SECRET_KEY = 'your_click_secret_key'
86
+ CLICK_ACCOUNT_MODEL = 'your_app.models.YourModel'
87
+ CLICK_COMMISSION_PERCENT = 0.0
88
+ ```
89
+
90
+ 2. Create webhook handlers:
91
+
92
+ ```python
93
+ # views.py
94
+ from paytechuz.integrations.django.views import PaymeWebhookView
95
+ from .models import Order
96
+
97
+ class PaymeWebhookView(PaymeWebhookView):
98
+ def successfully_payment(self, params, transaction):
99
+ order = Order.objects.get(id=transaction.account_id)
100
+ order.status = 'paid'
101
+ order.save()
102
+
103
+ def cancelled_payment(self, params, transaction):
104
+ order = Order.objects.get(id=transaction.account_id)
105
+ order.status = 'cancelled'
106
+ order.save()
107
+ ```
108
+
109
+ ### FastAPI Integration
110
+
111
+ 1. Create webhook handler:
112
+
113
+ ```python
114
+ from fastapi import FastAPI, Request
115
+ from paytechuz.integrations.fastapi import PaymeWebhookHandler
116
+
117
+ app = FastAPI()
118
+
119
+ class CustomPaymeWebhookHandler(PaymeWebhookHandler):
120
+ def successfully_payment(self, params, transaction):
121
+ # Handle successful payment
122
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
123
+ order.status = "paid"
124
+ self.db.commit()
125
+
126
+ def cancelled_payment(self, params, transaction):
127
+ # Handle cancelled payment
128
+ order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
129
+ order.status = "cancelled"
130
+ self.db.commit()
131
+
132
+ @app.post("/payments/payme/webhook")
133
+ async def payme_webhook(request: Request):
134
+ handler = CustomPaymeWebhookHandler(
135
+ payme_id="your_merchant_id",
136
+ payme_key="your_merchant_key"
137
+ )
138
+ return await handler.handle_webhook(request)
139
+ ```
140
+
141
+ ## Documentation
142
+
143
+ Detailed documentation is available in multiple languages:
144
+
145
+ - 📖 [English Documentation](docs/en/index.md)
146
+ - 📖 [O'zbek tilidagi hujjatlar](docs/uz/index.md)
@@ -0,0 +1,35 @@
1
+ core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ core/base.py,sha256=pW_0QrlyX0F8XVToeeDEI78ygAF2IgvvvyUGYMHH9bs,2565
3
+ core/constants.py,sha256=P2zeZ_cfZIttdC1vqkpIngkfRFh6loWzJYEgzQb5cKA,1660
4
+ core/exceptions.py,sha256=XMJkqiponTkvhjoh3S2iFNuU3UbBdFW4130kd0hpudg,5489
5
+ core/http.py,sha256=qmLR6ujxmIPjwoTS4vvKRIvDnNcpl84sS1HmVB890b0,7686
6
+ core/utils.py,sha256=ETHMzwu7_dirc-QfBUjpSTQmyS6_vBiNq97Dq9CNZto,4718
7
+ core/payme/errors.py,sha256=CZE62MbYDMsRfNIX23Syt6of_tPMMGLnXhYMii4hw3A,542
8
+ gateways/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ gateways/click/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ gateways/click/client.py,sha256=XTri3iogq4_MgvZehBK2CScVCMubvg1GNXV9whGAS6I,6663
11
+ gateways/click/merchant.py,sha256=vJ_DivA1KfRT5p3sfA5yZGMYXoUmVbAM7QHvaXr6VCU,7254
12
+ gateways/click/webhook.py,sha256=rph-NmjjnBKMW4rcxQTXrHHdK-uMrU39kXnbqK56leo,7936
13
+ gateways/payme/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ gateways/payme/cards.py,sha256=aL6su_ZTCBPU8qmrz2Jcw_Wn7Zf9RwIu8k10o4AWFTs,5420
15
+ gateways/payme/client.py,sha256=B4jxjX_7J_uR-NP8-B6wO_VrnwO_RRh1ATiKsO4ZinM,7855
16
+ gateways/payme/receipts.py,sha256=DdrZMPeDvQmGyqAEOTmtUorfcIVVb3t2tg31l7TXqHo,8904
17
+ gateways/payme/webhook.py,sha256=-0O8vzMtiu4U8FWFKDA6EfyoX4NEGqcEq-T0yNtVhM4,12374
18
+ integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ integrations/django/__init__.py,sha256=YDgD1Ux1E3CJxQMT-9tulhXmuLOEaUSv320LeaVgJ00,116
20
+ integrations/django/admin.py,sha256=6fs6GiKcdc-hGlLxJ0BthY7TFo_2RVVJRhQwhxMroCY,2664
21
+ integrations/django/apps.py,sha256=57svd2aqSuoASWMI3Jnh70ZXrYN1oQ8EnvLl_5LNyl0,473
22
+ integrations/django/models.py,sha256=83PjBnombavEjfPm2EcVFo2R5BO6VRIjp0MlNCp-fbg,5819
23
+ integrations/django/signals.py,sha256=VtNYEAnu13wi9PqadEaCU9LY_k2tY26AS4bnPIAqw7M,1319
24
+ integrations/django/views.py,sha256=nP2HRMx02tMbdv_KfDqIA5vQAwZ6TUuZazrZ2zoNfqQ,3029
25
+ integrations/django/webhooks.py,sha256=cP_Jc3VlyyvyzDbBd2yEVHikw60th1_-L9_vtsRfwgs,31335
26
+ integrations/django/migrations/0001_initial.py,sha256=YLDUp1w0V3Zvuz5TssQDrx3PlccduoHqdLD109Hg8Z4,2326
27
+ integrations/django/migrations/__init__.py,sha256=KLQ5NdjOMLDS21-u3b_g08G1MjPMMhG95XI_N8m4FSo,41
28
+ integrations/fastapi/__init__.py,sha256=DLnhAZQZf2ghu8BuFFfE7FzbNKWQQ2SLG8qxldRuwR4,565
29
+ integrations/fastapi/models.py,sha256=eWGUpiKufj47AK8Hld4A91jRDj0ZKQzAf95CyUozmvo,4638
30
+ integrations/fastapi/routes.py,sha256=D17QeyY4-aX6tCNmk5h3UiavukvVrE5e6JOFCy4t_n8,36629
31
+ integrations/fastapi/schemas.py,sha256=CkNohj22mQQje8Pu_IkTQwUPAoYHNOKXlGjqaRX_SGQ,3784
32
+ paytechuz-0.1.4.dist-info/METADATA,sha256=4QSapmW-hD_4AJm0rE-OvnXIhRRgsePVnsX6UKczfh4,3861
33
+ paytechuz-0.1.4.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
34
+ paytechuz-0.1.4.dist-info/top_level.txt,sha256=tfgxeqY7QDfOFb07FwoMkscdgKV9VIbJLnJnh2FNjRM,27
35
+ paytechuz-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ core
2
+ gateways
3
+ integrations
paytechuz/__init__.py DELETED
@@ -1,76 +0,0 @@
1
- """PayTechUZ - Unified payment library for Uzbekistan payment systems.
2
-
3
- This library provides a unified interface for working with Payme and Click
4
- payment systems in Uzbekistan. It supports Django, Flask, and FastAPI.
5
- """
6
- from typing import Any
7
-
8
- __version__ = '0.1.1'
9
-
10
-
11
-
12
- # Define dummy classes to avoid import errors
13
- class PaymeGateway:
14
- """Dummy PaymeGateway class to avoid import errors."""
15
- def __init__(self, **kwargs):
16
- pass
17
-
18
-
19
- class ClickGateway:
20
- """Dummy ClickGateway class to avoid import errors."""
21
- def __init__(self, **kwargs):
22
- pass
23
-
24
-
25
- class PaymentGateway:
26
- """Dummy PaymentGateway enum to avoid import errors."""
27
- class PAYME:
28
- value = 'payme'
29
-
30
- class CLICK:
31
- value = 'click'
32
-
33
-
34
- # Import framework integrations - these imports are used to check availability
35
- # of frameworks, not for direct usage
36
- try:
37
- import django # noqa: F401 - Used for availability check
38
- HAS_DJANGO = True
39
- except ImportError:
40
- HAS_DJANGO = False
41
-
42
- try:
43
- import fastapi # noqa: F401 - Used for availability check
44
- HAS_FASTAPI = True
45
- except ImportError:
46
- HAS_FASTAPI = False
47
-
48
- try:
49
- import flask # noqa: F401 - Used for availability check
50
- HAS_FLASK = True
51
- except ImportError:
52
- HAS_FLASK = False
53
-
54
-
55
- def create_gateway(gateway_type: str, **kwargs) -> Any:
56
- """
57
- Create a payment gateway instance.
58
-
59
- Args:
60
- gateway_type: Type of gateway ('payme' or 'click')
61
- **kwargs: Gateway-specific configuration
62
-
63
- Returns:
64
- Payment gateway instance
65
-
66
- Raises:
67
- ValueError: If the gateway type is not supported
68
- ImportError: If the required gateway module is not available
69
- """
70
- # Just use the dummy classes for now
71
- if gateway_type.lower() == 'payme':
72
- return PaymeGateway(**kwargs)
73
- if gateway_type.lower() == 'click':
74
- return ClickGateway(**kwargs)
75
-
76
- raise ValueError(f"Unsupported gateway type: {gateway_type}")
@@ -1,238 +0,0 @@
1
- """
2
- Payme payment gateway client.
3
- """
4
- import logging
5
- from typing import Dict, Any, Optional, Union
6
-
7
- from paytechuz.core.base import BasePaymentGateway
8
- from paytechuz.core.http import HttpClient
9
- from paytechuz.core.constants import PaymeNetworks
10
- from paytechuz.core.utils import format_amount, handle_exceptions
11
- from paytechuz.gateways.payme.cards import PaymeCards
12
- from paytechuz.gateways.payme.receipts import PaymeReceipts
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
- class PaymeGateway(BasePaymentGateway):
17
- """
18
- Payme payment gateway implementation.
19
-
20
- This class provides methods for interacting with the Payme payment gateway,
21
- including creating payments, checking payment status, and canceling payments.
22
- """
23
-
24
- def __init__(
25
- self,
26
- payme_id: str,
27
- payme_key: Optional[str] = None,
28
- fallback_id: Optional[str] = None,
29
- is_test_mode: bool = False
30
- ):
31
- """
32
- Initialize the Payme gateway.
33
-
34
- Args:
35
- payme_id: Payme merchant ID
36
- payme_key: Payme merchant key for authentication
37
- fallback_id: Fallback merchant ID
38
- is_test_mode: Whether to use the test environment
39
- """
40
- super().__init__(is_test_mode)
41
- self.payme_id = payme_id
42
- self.payme_key = payme_key
43
- self.fallback_id = fallback_id
44
-
45
- # Set the API URL based on the environment
46
- url = PaymeNetworks.TEST_NET if is_test_mode else PaymeNetworks.PROD_NET
47
-
48
- # Initialize HTTP client
49
- self.http_client = HttpClient(base_url=url)
50
-
51
- # Initialize components
52
- self.cards = PaymeCards(http_client=self.http_client, payme_id=payme_id)
53
- self.receipts = PaymeReceipts(
54
- http_client=self.http_client,
55
- payme_id=payme_id,
56
- payme_key=payme_key
57
- )
58
-
59
- @handle_exceptions
60
- def create_payment(
61
- self,
62
- amount: Union[int, float, str],
63
- account_id: Union[int, str],
64
- **kwargs
65
- ) -> Dict[str, Any]:
66
- """
67
- Create a payment using Payme receipts.
68
-
69
- Args:
70
- amount: The payment amount in som
71
- account_id: The account ID or order ID
72
- **kwargs: Additional parameters for the payment
73
- - description: Payment description
74
- - detail: Payment details
75
- - callback_url: URL to redirect after payment
76
- - return_url: URL to return after payment
77
- - phone: Customer phone number
78
- - email: Customer email
79
- - language: Language code (uz, ru, en)
80
- - expire_minutes: Payment expiration time in minutes
81
-
82
- Returns:
83
- Dict containing payment details including transaction ID and payment URL
84
- """
85
- # Format amount to tiyin (1 som = 100 tiyin)
86
- amount_tiyin = format_amount(amount)
87
-
88
- # Extract additional parameters
89
- description = kwargs.get('description', f'Payment for account {account_id}')
90
- detail = kwargs.get('detail', {})
91
- callback_url = kwargs.get('callback_url')
92
- return_url = kwargs.get('return_url')
93
- phone = kwargs.get('phone')
94
- email = kwargs.get('email')
95
- language = kwargs.get('language', 'uz')
96
- expire_minutes = kwargs.get('expire_minutes', 60) # Default 1 hour
97
-
98
- # Check if we have a merchant key
99
- if self.payme_key:
100
- # Create receipt using the API
101
- receipt_data = self.receipts.create(
102
- amount=amount_tiyin,
103
- account={"account_id": str(account_id)},
104
- description=description,
105
- detail=detail,
106
- callback_url=callback_url,
107
- return_url=return_url,
108
- phone=phone,
109
- email=email,
110
- language=language,
111
- expire_minutes=expire_minutes
112
- )
113
-
114
- # Extract receipt ID and payment URL
115
- receipt_id = receipt_data.get('receipt', {}).get('_id')
116
- payment_url = receipt_data.get('receipt', {}).get('pay_url')
117
-
118
- return {
119
- 'transaction_id': receipt_id,
120
- 'payment_url': payment_url,
121
- 'amount': amount,
122
- 'account_id': account_id,
123
- 'status': 'created',
124
- 'raw_response': receipt_data
125
- }
126
- else:
127
- # Generate a payment URL using payme-pkg style
128
- # This is a fallback method that doesn't require authentication
129
- import base64
130
- from paytechuz.core.utils import generate_id
131
-
132
- # Generate a unique transaction ID
133
- transaction_id = generate_id("payme")
134
-
135
- # Format amount to the smallest currency unit (tiyin)
136
- # amount_tiyin is already in tiyin format
137
-
138
- # Build the payment parameters string
139
- # Format: m=merchant_id;ac.field=value;a=amount;c=callback_url
140
- params_str = f"m={self.payme_id};ac.id={account_id};a={amount_tiyin}"
141
-
142
- # Add callback URL if provided (this is used for return URL in payme-pkg)
143
- if return_url:
144
- params_str += f";c={return_url}"
145
-
146
- # Encode the parameters string to base64
147
- encoded_params = base64.b64encode(params_str.encode("utf-8")).decode("utf-8")
148
-
149
- # Build the payment URL
150
- if self.is_test_mode:
151
- payment_url = f"https://test.paycom.uz/{encoded_params}"
152
- else:
153
- payment_url = f"https://checkout.paycom.uz/{encoded_params}"
154
-
155
- # Print the parameters for debugging
156
- print("Payme payment parameters:")
157
- print(f"Parameters string: {params_str}")
158
- print(f"Encoded parameters: {encoded_params}")
159
- print(f"Payment URL: {payment_url}")
160
-
161
- return {
162
- 'transaction_id': transaction_id,
163
- 'payment_url': payment_url,
164
- 'amount': amount,
165
- 'account_id': account_id,
166
- 'status': 'created',
167
- 'raw_response': {}
168
- }
169
-
170
- @handle_exceptions
171
- def check_payment(self, transaction_id: str) -> Dict[str, Any]:
172
- """
173
- Check payment status using Payme receipts.
174
-
175
- Args:
176
- transaction_id: The receipt ID to check
177
-
178
- Returns:
179
- Dict containing payment status and details
180
- """
181
- receipt_data = self.receipts.check(receipt_id=transaction_id)
182
-
183
- # Extract receipt status
184
- receipt = receipt_data.get('receipt', {})
185
- status = receipt.get('state')
186
-
187
- # Map Payme status to our status
188
- status_mapping = {
189
- 0: 'created',
190
- 1: 'waiting',
191
- 2: 'paid',
192
- 3: 'cancelled',
193
- 4: 'refunded'
194
- }
195
-
196
- mapped_status = status_mapping.get(status, 'unknown')
197
-
198
- return {
199
- 'transaction_id': transaction_id,
200
- 'status': mapped_status,
201
- 'amount': receipt.get('amount') / 100, # Convert from tiyin to som
202
- 'paid_at': receipt.get('pay_time'),
203
- 'created_at': receipt.get('create_time'),
204
- 'cancelled_at': receipt.get('cancel_time'),
205
- 'raw_response': receipt_data
206
- }
207
-
208
- @handle_exceptions
209
- def cancel_payment(
210
- self,
211
- transaction_id: str,
212
- reason: Optional[str] = None
213
- ) -> Dict[str, Any]:
214
- """
215
- Cancel payment using Payme receipts.
216
-
217
- Args:
218
- transaction_id: The receipt ID to cancel
219
- reason: Optional reason for cancellation
220
-
221
- Returns:
222
- Dict containing cancellation status and details
223
- """
224
- receipt_data = self.receipts.cancel(
225
- receipt_id=transaction_id,
226
- reason=reason or "Cancelled by merchant"
227
- )
228
-
229
- # Extract receipt status
230
- receipt = receipt_data.get('receipt', {})
231
- status = receipt.get('state')
232
-
233
- return {
234
- 'transaction_id': transaction_id,
235
- 'status': 'cancelled' if status == 3 else 'unknown',
236
- 'cancelled_at': receipt.get('cancel_time'),
237
- 'raw_response': receipt_data
238
- }
@@ -1,198 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: paytechuz
3
- Version: 0.1.3
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
- Keywords: paytechuz,payme,click,uzbekistan,payment,gateway,payment-gateway,payment-processing,django,flask,fastapi
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.6
14
- Classifier: Programming Language :: Python :: 3.7
15
- Classifier: Programming Language :: Python :: 3.8
16
- Classifier: Programming Language :: Python :: 3.9
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: License :: OSI Approved :: MIT License
21
- Classifier: Operating System :: OS Independent
22
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
- Requires-Python: >=3.6
24
- Description-Content-Type: text/markdown
25
- Requires-Dist: requests<3.0,>=2.0
26
- Requires-Dist: dataclasses<1.0,>=0.6; python_version < "3.7"
27
- Provides-Extra: django
28
- Requires-Dist: django<5.0,>=3.0; extra == "django"
29
- Requires-Dist: djangorestframework<4.0,>=3.0; extra == "django"
30
- Provides-Extra: fastapi
31
- Requires-Dist: fastapi<1.0.0,>=0.68.0; extra == "fastapi"
32
- Requires-Dist: sqlalchemy<3.0,>=1.4; extra == "fastapi"
33
- Requires-Dist: httpx<1.0,>=0.20; extra == "fastapi"
34
- Requires-Dist: python-multipart==0.0.20; extra == "fastapi"
35
- Requires-Dist: pydantic<2.0,>=1.8; extra == "fastapi"
36
- Provides-Extra: flask
37
- Requires-Dist: flask<3.0,>=2.0; extra == "flask"
38
- Requires-Dist: flask-sqlalchemy<3.0,>=2.5; extra == "flask"
39
- Dynamic: author
40
- Dynamic: author-email
41
- Dynamic: classifier
42
- Dynamic: description
43
- Dynamic: description-content-type
44
- Dynamic: home-page
45
- Dynamic: keywords
46
- Dynamic: license
47
- Dynamic: provides-extra
48
- Dynamic: requires-dist
49
- Dynamic: requires-python
50
- Dynamic: summary
51
-
52
- # paytechuz
53
-
54
- paytechuz is a unified payment library for integration with popular payment systems in Uzbekistan (Payme and Click).
55
-
56
- [![PyPI version](https://badge.fury.io/py/paytechuz.svg)](https://badge.fury.io/py/paytechuz)
57
- [![Python Versions](https://img.shields.io/pypi/pyversions/paytechuz.svg)](https://pypi.org/project/paytechuz/)
58
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
-
60
- ## Installation
61
-
62
- To install paytechuz with all dependencies:
63
-
64
- ```bash
65
- pip install paytechuz
66
- ```
67
-
68
- For specific framework support:
69
-
70
- ```bash
71
- # For Django
72
- pip install paytechuz[django]
73
-
74
- # For FastAPI
75
- pip install paytechuz[fastapi]
76
- ```
77
-
78
- ## Quick Start
79
-
80
- ### Django Integration
81
-
82
- 1. Add the app to your `INSTALLED_APPS`:
83
-
84
- ```python
85
- # settings.py
86
- INSTALLED_APPS = [
87
- # ...
88
- 'paytechuz.integrations.django',
89
- ]
90
-
91
- # Payme settings
92
- PAYME_ID = 'your_payme_merchant_id'
93
- PAYME_KEY = 'your_payme_merchant_key'
94
- PAYME_ACCOUNT_MODEL = 'your_app.YourModel' # For example: 'orders.Order'
95
- PAYME_ACCOUNT_FIELD = 'id' # Field for account identifier
96
- PAYME_AMOUNT_FIELD = 'amount' # Field for storing payment amount
97
- PAYME_ONE_TIME_PAYMENT = True # Allow only one payment per account
98
- ```
99
-
100
- 2. Set up the webhook URLs:
101
-
102
- ```python
103
- # urls.py
104
- from django.urls import path
105
- from django.views.decorators.csrf import csrf_exempt
106
-
107
- from your_app.views import PaymeWebhookView, ClickWebhookView
108
-
109
-
110
- urlpatterns = [
111
- # ...
112
- path('payments/payme/', csrf_exempt(PaymeWebhookView.as_view()), name='payme_webhook'),
113
- path('payments/click/', csrf_exempt(ClickWebhookView.as_view()), name='click_webhook'),
114
- ]
115
- ```
116
-
117
- 3. Create custom webhook handlers:
118
-
119
- ```python
120
- # views.py
121
- from paytechuz.integrations.django.views import PaymeWebhookView as BasePaymeWebhookView
122
- from .models import Order
123
-
124
- class PaymeWebhookView(BasePaymeWebhookView):
125
- def successfully_payment(self, params, transaction):
126
- """Called when payment is successful"""
127
- order_id = transaction.account_id
128
- order = Order.objects.get(id=order_id)
129
- order.status = 'paid'
130
- order.save()
131
-
132
- def cancelled_payment(self, params, transaction):
133
- """Called when payment is cancelled"""
134
- order_id = transaction.account_id
135
- order = Order.objects.get(id=order_id)
136
- order.status = 'cancelled'
137
- order.save()
138
- ```
139
-
140
- ### FastAPI Integration
141
-
142
- 1. Create a custom webhook handler:
143
-
144
- ```python
145
- from fastapi import APIRouter, Depends, Request
146
- from sqlalchemy.orm import Session
147
-
148
- from app.database.db import get_db
149
- from app.models.models import Order
150
- from paytechuz.integrations.fastapi import PaymeWebhookHandler
151
-
152
- # Payme configuration
153
- PAYME_ID = 'your_payme_id'
154
- PAYME_KEY = 'your_payme_key'
155
-
156
- class CustomPaymeWebhookHandler(PaymeWebhookHandler):
157
- def successfully_payment(self, params, transaction) -> None:
158
- """Called when payment is successful"""
159
- order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
160
- if order:
161
- order.status = "paid"
162
- self.db.commit()
163
-
164
- def cancelled_payment(self, params, transaction) -> None:
165
- """Called when payment is cancelled"""
166
- order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
167
- if order:
168
- order.status = "cancelled"
169
- self.db.commit()
170
- ```
171
-
172
- 2. Create a webhook endpoint:
173
-
174
- ```python
175
- router = APIRouter()
176
-
177
- @router.post("/payments/payme/webhook")
178
- async def payme_webhook(request: Request, db: Session = Depends(get_db)):
179
- """Handle Payme webhook requests"""
180
- handler = CustomPaymeWebhookHandler(
181
- db=db,
182
- payme_id=PAYME_ID,
183
- payme_key=PAYME_KEY,
184
- account_model=Order,
185
- account_field="id",
186
- amount_field="amount",
187
- one_time_payment=False
188
- )
189
- result = await handler.handle_webhook(request)
190
- return result
191
- ```
192
-
193
- ## Documentation
194
-
195
- For detailed documentation, see:
196
-
197
- - [English Documentation](paytechuz/docs/en/index.md)
198
- - [O'zbek tilidagi hujjatlar](paytechuz/docs/index.md)
@@ -1,36 +0,0 @@
1
- paytechuz/__init__.py,sha256=qLjm35Dc7UqnetOcbXgc_vP5FnzAq810h7T1YBhjeiQ,1937
2
- paytechuz/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- paytechuz/core/base.py,sha256=bHmRMYZ97Kj76a4kL7UMRcecnZF-rKnBYZddQvRtRmQ,2497
4
- paytechuz/core/constants.py,sha256=P2zeZ_cfZIttdC1vqkpIngkfRFh6loWzJYEgzQb5cKA,1660
5
- paytechuz/core/exceptions.py,sha256=XMJkqiponTkvhjoh3S2iFNuU3UbBdFW4130kd0hpudg,5489
6
- paytechuz/core/http.py,sha256=qmLR6ujxmIPjwoTS4vvKRIvDnNcpl84sS1HmVB890b0,7686
7
- paytechuz/core/utils.py,sha256=ETHMzwu7_dirc-QfBUjpSTQmyS6_vBiNq97Dq9CNZto,4718
8
- paytechuz/core/payme/errors.py,sha256=CZE62MbYDMsRfNIX23Syt6of_tPMMGLnXhYMii4hw3A,542
9
- paytechuz/gateways/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- paytechuz/gateways/click/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- paytechuz/gateways/click/client.py,sha256=XTri3iogq4_MgvZehBK2CScVCMubvg1GNXV9whGAS6I,6663
12
- paytechuz/gateways/click/merchant.py,sha256=vJ_DivA1KfRT5p3sfA5yZGMYXoUmVbAM7QHvaXr6VCU,7254
13
- paytechuz/gateways/click/webhook.py,sha256=rph-NmjjnBKMW4rcxQTXrHHdK-uMrU39kXnbqK56leo,7936
14
- paytechuz/gateways/payme/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- paytechuz/gateways/payme/cards.py,sha256=aL6su_ZTCBPU8qmrz2Jcw_Wn7Zf9RwIu8k10o4AWFTs,5420
16
- paytechuz/gateways/payme/client.py,sha256=C1OQWLo7LIAF3xbFOLyCaJ3xFwQaFVpaxWCYajCltTY,8201
17
- paytechuz/gateways/payme/receipts.py,sha256=DdrZMPeDvQmGyqAEOTmtUorfcIVVb3t2tg31l7TXqHo,8904
18
- paytechuz/gateways/payme/webhook.py,sha256=-0O8vzMtiu4U8FWFKDA6EfyoX4NEGqcEq-T0yNtVhM4,12374
19
- paytechuz/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- paytechuz/integrations/django/__init__.py,sha256=YDgD1Ux1E3CJxQMT-9tulhXmuLOEaUSv320LeaVgJ00,116
21
- paytechuz/integrations/django/admin.py,sha256=6fs6GiKcdc-hGlLxJ0BthY7TFo_2RVVJRhQwhxMroCY,2664
22
- paytechuz/integrations/django/apps.py,sha256=57svd2aqSuoASWMI3Jnh70ZXrYN1oQ8EnvLl_5LNyl0,473
23
- paytechuz/integrations/django/models.py,sha256=83PjBnombavEjfPm2EcVFo2R5BO6VRIjp0MlNCp-fbg,5819
24
- paytechuz/integrations/django/signals.py,sha256=VtNYEAnu13wi9PqadEaCU9LY_k2tY26AS4bnPIAqw7M,1319
25
- paytechuz/integrations/django/views.py,sha256=nP2HRMx02tMbdv_KfDqIA5vQAwZ6TUuZazrZ2zoNfqQ,3029
26
- paytechuz/integrations/django/webhooks.py,sha256=cP_Jc3VlyyvyzDbBd2yEVHikw60th1_-L9_vtsRfwgs,31335
27
- paytechuz/integrations/django/migrations/0001_initial.py,sha256=YLDUp1w0V3Zvuz5TssQDrx3PlccduoHqdLD109Hg8Z4,2326
28
- paytechuz/integrations/django/migrations/__init__.py,sha256=KLQ5NdjOMLDS21-u3b_g08G1MjPMMhG95XI_N8m4FSo,41
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.1.3.dist-info/METADATA,sha256=fLZlC2jeUhspOlm3nmb0HzKy29Qa6N2K2T-u4y06gfU,6058
34
- paytechuz-0.1.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
- paytechuz-0.1.3.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
36
- paytechuz-0.1.3.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- paytechuz
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes