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

@@ -0,0 +1,99 @@
1
+ """
2
+ FastAPI schemas for PayTechUZ.
3
+ """
4
+ from datetime import datetime
5
+ from typing import Dict, Any, Optional, List
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ class PaymentTransactionBase(BaseModel):
10
+ """
11
+ Base schema for payment transaction.
12
+ """
13
+ gateway: str = Field(..., description="Payment gateway (payme or click)")
14
+ transaction_id: str = Field(..., description="Transaction ID from the payment system")
15
+ account_id: str = Field(..., description="Account or order ID")
16
+ amount: float = Field(..., description="Payment amount")
17
+ state: int = Field(0, description="Transaction state")
18
+
19
+
20
+ class PaymentTransactionCreate(PaymentTransactionBase):
21
+ """
22
+ Schema for creating a payment transaction.
23
+ """
24
+ extra_data: Optional[Dict[str, Any]] = Field(None, description="Additional data for the transaction")
25
+
26
+ class PaymentTransaction(PaymentTransactionBase):
27
+ """
28
+ Schema for payment transaction.
29
+ """
30
+ id: int = Field(..., description="Transaction ID")
31
+ extra_data: Dict[str, Any] = Field({}, description="Additional data for the transaction")
32
+ created_at: datetime = Field(..., description="Creation timestamp")
33
+ 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")
36
+
37
+ class Config:
38
+ """
39
+ Pydantic configuration.
40
+ """
41
+ orm_mode = True
42
+
43
+
44
+ class PaymentTransactionList(BaseModel):
45
+ """
46
+ Schema for a list of payment transactions.
47
+ """
48
+ transactions: List[PaymentTransaction] = Field(..., description="List of transactions")
49
+ total: int = Field(..., description="Total number of transactions")
50
+
51
+ class PaymeWebhookRequest(BaseModel):
52
+ """
53
+ Schema for Payme webhook request.
54
+ """
55
+ method: str = Field(..., description="Method name")
56
+ params: Dict[str, Any] = Field(..., description="Method parameters")
57
+ id: int = Field(..., description="Request ID")
58
+
59
+
60
+ class PaymeWebhookResponse(BaseModel):
61
+ """
62
+ Schema for Payme webhook response.
63
+ """
64
+ jsonrpc: str = Field("2.0", description="JSON-RPC version")
65
+ id: int = Field(..., description="Request ID")
66
+ result: Dict[str, Any] = Field(..., description="Response result")
67
+
68
+ class PaymeWebhookErrorResponse(BaseModel):
69
+ """
70
+ Schema for Payme webhook error response.
71
+ """
72
+ jsonrpc: str = Field("2.0", description="JSON-RPC version")
73
+ id: int = Field(..., description="Request ID")
74
+ error: Dict[str, Any] = Field(..., description="Error details")
75
+
76
+
77
+ class ClickWebhookRequest(BaseModel):
78
+ """
79
+ Schema for Click webhook request.
80
+ """
81
+ click_trans_id: str = Field(..., description="Click transaction ID")
82
+ service_id: str = Field(..., description="Service ID")
83
+ merchant_trans_id: str = Field(..., description="Merchant transaction ID")
84
+ amount: str = Field(..., description="Payment amount")
85
+ action: str = Field(..., description="Action (0 - prepare, 1 - complete)")
86
+ sign_time: str = Field(..., description="Signature timestamp")
87
+ sign_string: str = Field(..., description="Signature string")
88
+ error: Optional[str] = Field(None, description="Error code")
89
+ error_note: Optional[str] = Field(None, description="Error note")
90
+
91
+ class ClickWebhookResponse(BaseModel):
92
+ """
93
+ Schema for Click webhook response.
94
+ """
95
+ click_trans_id: str = Field(..., description="Click transaction ID")
96
+ merchant_trans_id: str = Field(..., description="Merchant transaction ID")
97
+ merchant_prepare_id: Optional[int] = Field(None, description="Merchant prepare ID")
98
+ error: int = Field(0, description="Error code")
99
+ error_note: str = Field("Success", description="Error note")
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: paytechuz
3
+ Version: 0.1.0
4
+ Summary: Unified Python package for Uzbekistan payment gateways (Payme, Click)
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)
@@ -0,0 +1,36 @@
1
+ core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ core/base.py,sha256=bHmRMYZ97Kj76a4kL7UMRcecnZF-rKnBYZddQvRtRmQ,2497
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
+ gateways/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ gateways/click/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ gateways/click/client.py,sha256=XTri3iogq4_MgvZehBK2CScVCMubvg1GNXV9whGAS6I,6663
10
+ gateways/click/merchant.py,sha256=vJ_DivA1KfRT5p3sfA5yZGMYXoUmVbAM7QHvaXr6VCU,7254
11
+ gateways/click/webhook.py,sha256=rph-NmjjnBKMW4rcxQTXrHHdK-uMrU39kXnbqK56leo,7936
12
+ gateways/payme/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ gateways/payme/cards.py,sha256=aL6su_ZTCBPU8qmrz2Jcw_Wn7Zf9RwIu8k10o4AWFTs,5420
14
+ gateways/payme/client.py,sha256=C1OQWLo7LIAF3xbFOLyCaJ3xFwQaFVpaxWCYajCltTY,8201
15
+ gateways/payme/receipts.py,sha256=DdrZMPeDvQmGyqAEOTmtUorfcIVVb3t2tg31l7TXqHo,8904
16
+ gateways/payme/webhook.py,sha256=-0O8vzMtiu4U8FWFKDA6EfyoX4NEGqcEq-T0yNtVhM4,12374
17
+ integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ integrations/django/__init__.py,sha256=YDgD1Ux1E3CJxQMT-9tulhXmuLOEaUSv320LeaVgJ00,116
19
+ integrations/django/admin.py,sha256=6fs6GiKcdc-hGlLxJ0BthY7TFo_2RVVJRhQwhxMroCY,2664
20
+ integrations/django/apps.py,sha256=57svd2aqSuoASWMI3Jnh70ZXrYN1oQ8EnvLl_5LNyl0,473
21
+ integrations/django/models.py,sha256=83PjBnombavEjfPm2EcVFo2R5BO6VRIjp0MlNCp-fbg,5819
22
+ integrations/django/signals.py,sha256=VtNYEAnu13wi9PqadEaCU9LY_k2tY26AS4bnPIAqw7M,1319
23
+ integrations/django/views.py,sha256=nP2HRMx02tMbdv_KfDqIA5vQAwZ6TUuZazrZ2zoNfqQ,3029
24
+ integrations/django/webhooks.py,sha256=cP_Jc3VlyyvyzDbBd2yEVHikw60th1_-L9_vtsRfwgs,31335
25
+ integrations/django/migrations/0001_initial.py,sha256=YLDUp1w0V3Zvuz5TssQDrx3PlccduoHqdLD109Hg8Z4,2326
26
+ integrations/django/migrations/__init__.py,sha256=KLQ5NdjOMLDS21-u3b_g08G1MjPMMhG95XI_N8m4FSo,41
27
+ integrations/fastapi/__init__.py,sha256=DLnhAZQZf2ghu8BuFFfE7FzbNKWQQ2SLG8qxldRuwR4,565
28
+ integrations/fastapi/models.py,sha256=eWGUpiKufj47AK8Hld4A91jRDj0ZKQzAf95CyUozmvo,4638
29
+ integrations/fastapi/routes.py,sha256=D17QeyY4-aX6tCNmk5h3UiavukvVrE5e6JOFCy4t_n8,36629
30
+ integrations/fastapi/schemas.py,sha256=CkNohj22mQQje8Pu_IkTQwUPAoYHNOKXlGjqaRX_SGQ,3784
31
+ tests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
32
+ tests/test_gateway.py,sha256=BlFu1rpUkwvyvz0FnMW0Ql2h1_oolKsiu1O29i4_5tY,2028
33
+ paytechuz-0.1.0.dist-info/METADATA,sha256=pAGMLutaf31x74N9PXdcu-QzUVZ-lO27M6tpPwKshNY,6073
34
+ paytechuz-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
+ paytechuz-0.1.0.dist-info/top_level.txt,sha256=R3fFUS1l6Qekw12fn3uEDdEXoU3hM1KY6QsBFpGWEh0,33
36
+ paytechuz-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,4 @@
1
+ core
2
+ gateways
3
+ integrations
4
+ tests
tests/__init__.py ADDED
@@ -0,0 +1 @@
1
+
tests/test_gateway.py ADDED
@@ -0,0 +1,70 @@
1
+ """
2
+ Tests for the gateway module.
3
+ """
4
+ import unittest
5
+ from unittest.mock import patch, MagicMock
6
+
7
+ from paytechuz import create_gateway
8
+
9
+
10
+ class TestCreateGateway(unittest.TestCase):
11
+ """
12
+ Test the create_gateway function.
13
+ """
14
+
15
+ def test_create_payme_gateway(self):
16
+ """
17
+ Test creating a Payme gateway.
18
+ """
19
+ with patch('paytechuz.gateway.PaymeGateway') as mock_payme:
20
+ mock_instance = MagicMock()
21
+ mock_payme.return_value = mock_instance
22
+
23
+ gateway = create_gateway(
24
+ 'payme',
25
+ payme_id='test-id',
26
+ payme_key='test-key',
27
+ is_test_mode=True
28
+ )
29
+
30
+ self.assertEqual(gateway, mock_instance)
31
+ mock_payme.assert_called_once_with(
32
+ payme_id='test-id',
33
+ payme_key='test-key',
34
+ is_test_mode=True
35
+ )
36
+
37
+ def test_create_click_gateway(self):
38
+ """
39
+ Test creating a Click gateway.
40
+ """
41
+ with patch('paytechuz.gateway.ClickGateway') as mock_click:
42
+ mock_instance = MagicMock()
43
+ mock_click.return_value = mock_instance
44
+
45
+ gateway = create_gateway(
46
+ 'click',
47
+ service_id='test-service-id',
48
+ merchant_id='test-merchant-id',
49
+ secret_key='test-secret-key',
50
+ is_test_mode=True
51
+ )
52
+
53
+ self.assertEqual(gateway, mock_instance)
54
+ mock_click.assert_called_once_with(
55
+ service_id='test-service-id',
56
+ merchant_id='test-merchant-id',
57
+ secret_key='test-secret-key',
58
+ is_test_mode=True
59
+ )
60
+
61
+ def test_invalid_gateway_type(self):
62
+ """
63
+ Test creating a gateway with an invalid type.
64
+ """
65
+ with self.assertRaises(ValueError):
66
+ create_gateway('invalid')
67
+
68
+
69
+ if __name__ == '__main__':
70
+ unittest.main()