paytechuz 0.1.3__tar.gz → 0.1.4__tar.gz
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.
- paytechuz-0.1.4/PKG-INFO +146 -0
- paytechuz-0.1.4/README.md +131 -0
- paytechuz-0.1.4/pyproject.toml +18 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/setup.py +3 -15
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/__init__.py +1 -2
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/base.py +12 -3
- paytechuz-0.1.4/src/gateways/payme/client.py +262 -0
- paytechuz-0.1.4/src/paytechuz.egg-info/PKG-INFO +146 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4/src}/paytechuz.egg-info/SOURCES.txt +6 -6
- paytechuz-0.1.4/src/paytechuz.egg-info/top_level.txt +3 -0
- paytechuz-0.1.3/PKG-INFO +0 -198
- paytechuz-0.1.3/README.md +0 -147
- paytechuz-0.1.3/paytechuz.egg-info/PKG-INFO +0 -198
- paytechuz-0.1.3/paytechuz.egg-info/requires.txt +0 -19
- paytechuz-0.1.3/paytechuz.egg-info/top_level.txt +0 -1
- paytechuz-0.1.3/src/gateways/payme/client.py +0 -238
- {paytechuz-0.1.3 → paytechuz-0.1.4}/MANIFEST.in +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/setup.cfg +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/constants.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/exceptions.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/http.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/payme/errors.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/core/utils.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/click/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/click/client.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/click/merchant.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/click/webhook.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/payme/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/payme/cards.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/payme/receipts.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/gateways/payme/webhook.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/admin.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/apps.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/migrations/0001_initial.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/migrations/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/models.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/signals.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/views.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/django/webhooks.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/fastapi/__init__.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/fastapi/models.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/fastapi/routes.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4}/src/integrations/fastapi/schemas.py +0 -0
- {paytechuz-0.1.3 → paytechuz-0.1.4/src}/paytechuz.egg-info/dependency_links.txt +0 -0
paytechuz-0.1.4/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://badge.fury.io/py/paytechuz)
|
|
19
|
+
[](https://pypi.org/project/paytechuz/)
|
|
20
|
+
[](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,131 @@
|
|
|
1
|
+
# PayTechUZ
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/paytechuz)
|
|
4
|
+
[](https://pypi.org/project/paytechuz/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
### Basic Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install paytechuz
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Framework-Specific Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# For Django
|
|
18
|
+
pip install paytechuz[django]
|
|
19
|
+
|
|
20
|
+
# For FastAPI
|
|
21
|
+
pip install paytechuz[fastapi]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Generate Payment Links
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from paytechuz.gateways.payme import PaymeGateway
|
|
30
|
+
from paytechuz.gateways.click import ClickGateway
|
|
31
|
+
|
|
32
|
+
# Initialize gateways
|
|
33
|
+
payme = PaymeGateway()
|
|
34
|
+
click = ClickGateway()
|
|
35
|
+
|
|
36
|
+
# Generate payment links
|
|
37
|
+
payme_link = payme.generate_payment_link(
|
|
38
|
+
id="order_123",
|
|
39
|
+
amount=150000, # amount in UZS
|
|
40
|
+
return_url="https://example.com/return"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
click_link = click.generate_payment_link(
|
|
44
|
+
id="order_123",
|
|
45
|
+
amount=150000, # amount in UZS
|
|
46
|
+
return_url="https://example.com/return"
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Django Integration
|
|
51
|
+
|
|
52
|
+
1. Add to `INSTALLED_APPS`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# settings.py
|
|
56
|
+
INSTALLED_APPS = [
|
|
57
|
+
# ...
|
|
58
|
+
'paytechuz.integrations.django',
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
PAYME_ID = 'your_payme_merchant_id'
|
|
62
|
+
PAYME_KEY = 'your_payme_merchant_key'
|
|
63
|
+
PAYME_ACCOUNT_MODEL = 'your_app.models.YourModel' # For example: 'orders.models.Order'
|
|
64
|
+
PAYME_ACCOUNT_FIELD = 'id'
|
|
65
|
+
PAYME_AMOUNT_FIELD = 'amount' # Field for storing payment amount
|
|
66
|
+
PAYME_ONE_TIME_PAYMENT = True # Allow only one payment per account
|
|
67
|
+
|
|
68
|
+
CLICK_SERVICE_ID = 'your_click_service_id'
|
|
69
|
+
CLICK_MERCHANT_ID = 'your_click_merchant_id'
|
|
70
|
+
CLICK_SECRET_KEY = 'your_click_secret_key'
|
|
71
|
+
CLICK_ACCOUNT_MODEL = 'your_app.models.YourModel'
|
|
72
|
+
CLICK_COMMISSION_PERCENT = 0.0
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Create webhook handlers:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# views.py
|
|
79
|
+
from paytechuz.integrations.django.views import PaymeWebhookView
|
|
80
|
+
from .models import Order
|
|
81
|
+
|
|
82
|
+
class PaymeWebhookView(PaymeWebhookView):
|
|
83
|
+
def successfully_payment(self, params, transaction):
|
|
84
|
+
order = Order.objects.get(id=transaction.account_id)
|
|
85
|
+
order.status = 'paid'
|
|
86
|
+
order.save()
|
|
87
|
+
|
|
88
|
+
def cancelled_payment(self, params, transaction):
|
|
89
|
+
order = Order.objects.get(id=transaction.account_id)
|
|
90
|
+
order.status = 'cancelled'
|
|
91
|
+
order.save()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### FastAPI Integration
|
|
95
|
+
|
|
96
|
+
1. Create webhook handler:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from fastapi import FastAPI, Request
|
|
100
|
+
from paytechuz.integrations.fastapi import PaymeWebhookHandler
|
|
101
|
+
|
|
102
|
+
app = FastAPI()
|
|
103
|
+
|
|
104
|
+
class CustomPaymeWebhookHandler(PaymeWebhookHandler):
|
|
105
|
+
def successfully_payment(self, params, transaction):
|
|
106
|
+
# Handle successful payment
|
|
107
|
+
order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
|
|
108
|
+
order.status = "paid"
|
|
109
|
+
self.db.commit()
|
|
110
|
+
|
|
111
|
+
def cancelled_payment(self, params, transaction):
|
|
112
|
+
# Handle cancelled payment
|
|
113
|
+
order = self.db.query(Order).filter(Order.id == transaction.account_id).first()
|
|
114
|
+
order.status = "cancelled"
|
|
115
|
+
self.db.commit()
|
|
116
|
+
|
|
117
|
+
@app.post("/payments/payme/webhook")
|
|
118
|
+
async def payme_webhook(request: Request):
|
|
119
|
+
handler = CustomPaymeWebhookHandler(
|
|
120
|
+
payme_id="your_merchant_id",
|
|
121
|
+
payme_key="your_merchant_key"
|
|
122
|
+
)
|
|
123
|
+
return await handler.handle_webhook(request)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Documentation
|
|
127
|
+
|
|
128
|
+
Detailed documentation is available in multiple languages:
|
|
129
|
+
|
|
130
|
+
- 📖 [English Documentation](docs/en/index.md)
|
|
131
|
+
- 📖 [O'zbek tilidagi hujjatlar](docs/uz/index.md)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "paytechuz"
|
|
7
|
+
version = "0.1.4"
|
|
8
|
+
description = "Unified Python package for Uzbekistan payment gateways"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
|
|
13
|
+
[tool.setuptools]
|
|
14
|
+
package-dir = {"" = "src"}
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
18
|
+
include = ["paytechuz*", "core*", "gateways*", "integrations*"]
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""Setup script for PayTechUZ package."""
|
|
2
2
|
|
|
3
3
|
import pathlib
|
|
4
|
-
from setuptools import setup
|
|
4
|
+
from setuptools import setup, find_namespace_packages
|
|
5
5
|
|
|
6
6
|
here = pathlib.Path(__file__).parent.resolve()
|
|
7
7
|
long_description = (here / "README.md").read_text(encoding="utf-8")
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name='paytechuz',
|
|
11
|
-
version='0.1.
|
|
11
|
+
version='0.1.4',
|
|
12
12
|
license='MIT',
|
|
13
13
|
author="Muhammadali Akbarov",
|
|
14
14
|
author_email='muhammadali17abc@gmail.com',
|
|
@@ -17,19 +17,7 @@ setup(
|
|
|
17
17
|
long_description_content_type="text/markdown",
|
|
18
18
|
url='https://github.com/Muhammadali-Akbarov/paytechuz',
|
|
19
19
|
|
|
20
|
-
packages=[
|
|
21
|
-
'paytechuz',
|
|
22
|
-
'paytechuz.core',
|
|
23
|
-
'paytechuz.core.payme',
|
|
24
|
-
'paytechuz.gateways',
|
|
25
|
-
'paytechuz.gateways.payme',
|
|
26
|
-
'paytechuz.gateways.click',
|
|
27
|
-
'paytechuz.integrations',
|
|
28
|
-
'paytechuz.integrations.django',
|
|
29
|
-
'paytechuz.integrations.django.migrations',
|
|
30
|
-
'paytechuz.integrations.fastapi',
|
|
31
|
-
'paytechuz.integrations.flask',
|
|
32
|
-
],
|
|
20
|
+
packages=find_namespace_packages(include=['paytechuz*']),
|
|
33
21
|
package_dir={
|
|
34
22
|
'paytechuz': 'src',
|
|
35
23
|
},
|
|
@@ -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(
|
|
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(
|
|
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
|
+
}
|