paytechuz 0.2.24__py3-none-any.whl → 0.3.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.
- paytechuz/__init__.py +6 -3
- paytechuz/core/constants.py +25 -0
- paytechuz/gateways/atmos/__init__.py +2 -0
- paytechuz/gateways/atmos/client.py +212 -0
- paytechuz/gateways/atmos/webhook.py +99 -0
- paytechuz/integrations/django/migrations/0002_alter_paymenttransaction_gateway.py +18 -0
- paytechuz/integrations/django/models.py +2 -0
- paytechuz/integrations/django/views.py +37 -1
- paytechuz/integrations/django/webhooks.py +131 -0
- {paytechuz-0.2.24.dist-info → paytechuz-0.3.0.dist-info}/METADATA +187 -5
- {paytechuz-0.2.24.dist-info → paytechuz-0.3.0.dist-info}/RECORD +13 -9
- {paytechuz-0.2.24.dist-info → paytechuz-0.3.0.dist-info}/WHEEL +0 -0
- {paytechuz-0.2.24.dist-info → paytechuz-0.3.0.dist-info}/top_level.txt +0 -0
paytechuz/__init__.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
PayTechUZ - Unified payment library for Uzbekistan payment systems.
|
|
3
3
|
|
|
4
|
-
This library provides a unified interface for working with Payme and
|
|
4
|
+
This library provides a unified interface for working with Payme, Click, and Atmos
|
|
5
5
|
payment systems in Uzbekistan. It supports Django, Flask, and FastAPI.
|
|
6
6
|
"""
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
__version__ = '0.
|
|
9
|
+
__version__ = '0.3.0'
|
|
10
10
|
|
|
11
11
|
# Import framework integrations - these imports are used to check availability
|
|
12
12
|
# of frameworks, not for direct usage
|
|
@@ -31,6 +31,7 @@ except ImportError:
|
|
|
31
31
|
from paytechuz.core.base import BasePaymentGateway # noqa: E402
|
|
32
32
|
from paytechuz.gateways.payme.client import PaymeGateway # noqa: E402
|
|
33
33
|
from paytechuz.gateways.click.client import ClickGateway # noqa: E402
|
|
34
|
+
from paytechuz.gateways.atmos.client import AtmosGateway # noqa: E402
|
|
34
35
|
from paytechuz.core.constants import PaymentGateway # noqa: E402
|
|
35
36
|
|
|
36
37
|
|
|
@@ -39,7 +40,7 @@ def create_gateway(gateway_type: str, **kwargs) -> BasePaymentGateway:
|
|
|
39
40
|
Create a payment gateway instance.
|
|
40
41
|
|
|
41
42
|
Args:
|
|
42
|
-
gateway_type: Type of gateway ('payme' or '
|
|
43
|
+
gateway_type: Type of gateway ('payme', 'click', or 'atmos')
|
|
43
44
|
**kwargs: Gateway-specific configuration
|
|
44
45
|
|
|
45
46
|
Returns:
|
|
@@ -53,5 +54,7 @@ def create_gateway(gateway_type: str, **kwargs) -> BasePaymentGateway:
|
|
|
53
54
|
return PaymeGateway(**kwargs)
|
|
54
55
|
if gateway_type.lower() == PaymentGateway.CLICK.value:
|
|
55
56
|
return ClickGateway(**kwargs)
|
|
57
|
+
if gateway_type.lower() == PaymentGateway.ATMOS.value:
|
|
58
|
+
return AtmosGateway(**kwargs)
|
|
56
59
|
|
|
57
60
|
raise ValueError(f"Unsupported gateway type: {gateway_type}")
|
paytechuz/core/constants.py
CHANGED
|
@@ -16,6 +16,7 @@ class PaymentGateway(Enum):
|
|
|
16
16
|
"""Payment gateway types."""
|
|
17
17
|
PAYME = "payme"
|
|
18
18
|
CLICK = "click"
|
|
19
|
+
ATMOS = "atmos"
|
|
19
20
|
|
|
20
21
|
class PaymeEndpoints:
|
|
21
22
|
"""Payme API endpoints."""
|
|
@@ -66,3 +67,27 @@ class PaymeCancelReason:
|
|
|
66
67
|
REASON_CANCELLED_BY_USER = 7
|
|
67
68
|
REASON_SUSPICIOUS_OPERATION = 8
|
|
68
69
|
REASON_MERCHANT_DECISION = 9
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AtmosEndpoints:
|
|
73
|
+
"""Atmos API endpoints."""
|
|
74
|
+
TOKEN = "/token"
|
|
75
|
+
CREATE_PAYMENT = "/merchant/pay/create"
|
|
76
|
+
CHECK_PAYMENT = "/merchant/pay/get-status"
|
|
77
|
+
CANCEL_PAYMENT = "/merchant/pay/cancel"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AtmosNetworks:
|
|
81
|
+
"""Atmos API networks."""
|
|
82
|
+
PROD_NET = "https://partner.atmos.uz"
|
|
83
|
+
TEST_CHECKOUT = "https://test-checkout.pays.uz/invoice/get"
|
|
84
|
+
PROD_CHECKOUT = "https://checkout.pays.uz/invoice/get"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AtmosTransactionStatus:
|
|
88
|
+
"""Atmos transaction status codes."""
|
|
89
|
+
CREATED = "created"
|
|
90
|
+
PENDING = "pending"
|
|
91
|
+
SUCCESS = "success"
|
|
92
|
+
FAILED = "failed"
|
|
93
|
+
CANCELLED = "cancelled"
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Atmos payment gateway client.
|
|
3
|
+
"""
|
|
4
|
+
import base64
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Dict, Any, Optional, Union
|
|
7
|
+
|
|
8
|
+
from paytechuz.core.base import BasePaymentGateway
|
|
9
|
+
from paytechuz.core.http import HttpClient
|
|
10
|
+
from paytechuz.core.utils import handle_exceptions
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AtmosGateway(BasePaymentGateway):
|
|
16
|
+
"""
|
|
17
|
+
Atmos payment gateway implementation.
|
|
18
|
+
|
|
19
|
+
This class provides methods for interacting with the Atmos payment gateway,
|
|
20
|
+
including creating payments, checking payment status, and canceling
|
|
21
|
+
payments.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
consumer_key: str,
|
|
27
|
+
consumer_secret: str,
|
|
28
|
+
store_id: str,
|
|
29
|
+
terminal_id: Optional[str] = None,
|
|
30
|
+
is_test_mode: bool = False
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the Atmos gateway.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
consumer_key: Atmos consumer key
|
|
37
|
+
consumer_secret: Atmos consumer secret
|
|
38
|
+
store_id: Atmos store ID
|
|
39
|
+
terminal_id: Atmos terminal ID (optional)
|
|
40
|
+
is_test_mode: Whether to use the test environment
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(is_test_mode)
|
|
43
|
+
self.consumer_key = consumer_key
|
|
44
|
+
self.consumer_secret = consumer_secret
|
|
45
|
+
self.store_id = store_id
|
|
46
|
+
self.terminal_id = terminal_id
|
|
47
|
+
|
|
48
|
+
# Base URL is hard coded as per requirements
|
|
49
|
+
self.base_url = 'https://partner.atmos.uz'
|
|
50
|
+
|
|
51
|
+
# Initialize HTTP client
|
|
52
|
+
self.client = HttpClient(base_url=self.base_url)
|
|
53
|
+
|
|
54
|
+
# Get access token
|
|
55
|
+
self._access_token = None
|
|
56
|
+
self._get_access_token()
|
|
57
|
+
|
|
58
|
+
def _get_access_token(self) -> str:
|
|
59
|
+
"""Get access token for API authentication."""
|
|
60
|
+
try:
|
|
61
|
+
# Create basic auth header
|
|
62
|
+
credentials = f"{self.consumer_key}:{self.consumer_secret}"
|
|
63
|
+
encoded_credentials = base64.b64encode(
|
|
64
|
+
credentials.encode('utf-8')).decode('utf-8')
|
|
65
|
+
|
|
66
|
+
headers = {
|
|
67
|
+
'Authorization': f'Basic {encoded_credentials}',
|
|
68
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
data = {'grant_type': 'client_credentials'}
|
|
72
|
+
|
|
73
|
+
response = self.client.post('/token', data=data, headers=headers)
|
|
74
|
+
|
|
75
|
+
if response.get('access_token'):
|
|
76
|
+
self._access_token = response['access_token']
|
|
77
|
+
return self._access_token
|
|
78
|
+
|
|
79
|
+
raise ValueError("Failed to get access token")
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error("Error getting access token: %s", e)
|
|
83
|
+
raise
|
|
84
|
+
|
|
85
|
+
def _make_request(self, endpoint: str,
|
|
86
|
+
data: Dict[str, Any]) -> Dict[str, Any]:
|
|
87
|
+
"""Make authenticated request to Atmos API."""
|
|
88
|
+
if not self._access_token:
|
|
89
|
+
self._get_access_token()
|
|
90
|
+
|
|
91
|
+
headers = {
|
|
92
|
+
'Authorization': f'Bearer {self._access_token}',
|
|
93
|
+
'Content-Type': 'application/json'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
response = self.client.post(endpoint, json_data=data,
|
|
98
|
+
headers=headers)
|
|
99
|
+
return response
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error("API request failed: %s", e)
|
|
102
|
+
raise
|
|
103
|
+
|
|
104
|
+
@handle_exceptions
|
|
105
|
+
def create_payment(
|
|
106
|
+
self,
|
|
107
|
+
account_id: Union[int, str],
|
|
108
|
+
amount: Union[int, float, str],
|
|
109
|
+
**kwargs
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Create a payment transaction.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
account_id: The account ID or order ID
|
|
116
|
+
amount: The payment amount
|
|
117
|
+
**kwargs: Additional parameters
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dict containing payment details including transaction ID and
|
|
121
|
+
payment URL
|
|
122
|
+
"""
|
|
123
|
+
# Convert amount to tiyin (multiply by 100)
|
|
124
|
+
amount_tiyin = int(float(amount) * 100)
|
|
125
|
+
|
|
126
|
+
# Prepare request data
|
|
127
|
+
create_data = {
|
|
128
|
+
'amount': amount_tiyin,
|
|
129
|
+
'account': str(account_id),
|
|
130
|
+
'store_id': self.store_id
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Add terminal_id if provided
|
|
134
|
+
if self.terminal_id:
|
|
135
|
+
create_data['terminal_id'] = self.terminal_id
|
|
136
|
+
|
|
137
|
+
# Create transaction
|
|
138
|
+
response = self._make_request('/merchant/pay/create', create_data)
|
|
139
|
+
transaction_id = response['transaction_id']
|
|
140
|
+
|
|
141
|
+
# Generate payment URL
|
|
142
|
+
if self.is_test_mode:
|
|
143
|
+
base_url = "https://test-checkout.pays.uz/invoice/get"
|
|
144
|
+
else:
|
|
145
|
+
base_url = "https://checkout.pays.uz/invoice/get"
|
|
146
|
+
|
|
147
|
+
payment_url = (f"{base_url}?storeId={self.store_id}"
|
|
148
|
+
f"&transactionId={transaction_id}")
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
'transaction_id': transaction_id,
|
|
152
|
+
'payment_url': payment_url,
|
|
153
|
+
'amount': amount,
|
|
154
|
+
'account': str(account_id),
|
|
155
|
+
'status': 'created'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@handle_exceptions
|
|
159
|
+
def check_payment(self, transaction_id: str) -> Dict[str, Any]:
|
|
160
|
+
"""
|
|
161
|
+
Check payment status.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
transaction_id: The transaction ID to check
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dict containing payment status and details
|
|
168
|
+
"""
|
|
169
|
+
data = {
|
|
170
|
+
'transaction_id': transaction_id,
|
|
171
|
+
'store_id': self.store_id
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
response = self._make_request('/merchant/pay/get-status', data)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
'transaction_id': transaction_id,
|
|
178
|
+
'status': response.get('status', 'unknown'),
|
|
179
|
+
'details': response
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@handle_exceptions
|
|
183
|
+
def cancel_payment(
|
|
184
|
+
self,
|
|
185
|
+
transaction_id: str,
|
|
186
|
+
reason: Optional[str] = None
|
|
187
|
+
) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Cancel payment.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
transaction_id: The transaction ID to cancel
|
|
193
|
+
reason: Optional reason for cancellation
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Dict containing cancellation status and details
|
|
197
|
+
"""
|
|
198
|
+
data = {
|
|
199
|
+
'transaction_id': transaction_id,
|
|
200
|
+
'store_id': self.store_id
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if reason:
|
|
204
|
+
data['reason'] = reason
|
|
205
|
+
|
|
206
|
+
response = self._make_request('/merchant/pay/cancel', data)
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
'transaction_id': transaction_id,
|
|
210
|
+
'status': 'cancelled',
|
|
211
|
+
'details': response
|
|
212
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Atmos webhook handler.
|
|
3
|
+
"""
|
|
4
|
+
import hashlib
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
from paytechuz.core.base import BaseWebhookHandler
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AtmosWebhookHandler(BaseWebhookHandler):
|
|
14
|
+
"""
|
|
15
|
+
Atmos webhook handler for processing payment notifications.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, api_key: str):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the webhook handler.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
api_key: API key for signature verification
|
|
24
|
+
"""
|
|
25
|
+
self.api_key = api_key
|
|
26
|
+
|
|
27
|
+
def verify_signature(self, webhook_data: Dict[str, Any],
|
|
28
|
+
received_signature: str) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Verify webhook signature from Atmos.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
webhook_data: The webhook data received
|
|
34
|
+
received_signature: The signature received from Atmos
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
bool: True if signature is valid, False otherwise
|
|
38
|
+
"""
|
|
39
|
+
# Extract data from webhook
|
|
40
|
+
store_id = str(webhook_data.get('store_id', ''))
|
|
41
|
+
transaction_id = str(webhook_data.get('transaction_id', ''))
|
|
42
|
+
invoice = str(webhook_data.get('invoice', ''))
|
|
43
|
+
amount = str(webhook_data.get('amount', ''))
|
|
44
|
+
|
|
45
|
+
# Create signature string:
|
|
46
|
+
# store_id+transaction_id+invoice+amount+api_key
|
|
47
|
+
signature_string = (f"{store_id}{transaction_id}{invoice}"
|
|
48
|
+
f"{amount}{self.api_key}")
|
|
49
|
+
|
|
50
|
+
# Generate MD5 hash
|
|
51
|
+
calculated_signature = hashlib.md5(
|
|
52
|
+
signature_string.encode('utf-8')).hexdigest()
|
|
53
|
+
|
|
54
|
+
# Compare signatures
|
|
55
|
+
return calculated_signature == received_signature
|
|
56
|
+
|
|
57
|
+
def handle_webhook(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Handle webhook data from Atmos.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
data: The webhook data received from Atmos
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dict containing the response to be sent back to Atmos
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
# Extract signature
|
|
69
|
+
received_signature = data.get('sign', '')
|
|
70
|
+
|
|
71
|
+
# Verify signature
|
|
72
|
+
if not self.verify_signature(data, received_signature):
|
|
73
|
+
logger.error("Invalid webhook signature")
|
|
74
|
+
return {
|
|
75
|
+
'status': 0,
|
|
76
|
+
'message': 'Invalid signature'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Extract webhook data
|
|
80
|
+
transaction_id = data.get('transaction_id')
|
|
81
|
+
amount = data.get('amount')
|
|
82
|
+
invoice = data.get('invoice')
|
|
83
|
+
|
|
84
|
+
logger.info("Webhook received for transaction %s, "
|
|
85
|
+
"invoice %s, amount %s",
|
|
86
|
+
transaction_id, invoice, amount)
|
|
87
|
+
|
|
88
|
+
# Return success response
|
|
89
|
+
return {
|
|
90
|
+
'status': 1,
|
|
91
|
+
'message': 'Успешно'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
except ValueError as e:
|
|
95
|
+
logger.error("Webhook processing error: %s", e)
|
|
96
|
+
return {
|
|
97
|
+
'status': 0,
|
|
98
|
+
'message': f'Error: {str(e)}'
|
|
99
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2025-08-19 08:13
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('django', '0001_initial'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name='paymenttransaction',
|
|
15
|
+
name='gateway',
|
|
16
|
+
field=models.CharField(choices=[('payme', 'Payme'), ('click', 'Click'), ('atmos', 'Atmos')], max_length=10),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -12,10 +12,12 @@ class PaymentTransaction(models.Model):
|
|
|
12
12
|
# Payment gateway choices
|
|
13
13
|
PAYME = 'payme'
|
|
14
14
|
CLICK = 'click'
|
|
15
|
+
ATMOS = 'atmos'
|
|
15
16
|
|
|
16
17
|
GATEWAY_CHOICES = [
|
|
17
18
|
(PAYME, 'Payme'),
|
|
18
19
|
(CLICK, 'Click'),
|
|
20
|
+
(ATMOS, 'Atmos'),
|
|
19
21
|
]
|
|
20
22
|
|
|
21
23
|
# Transaction states
|
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
from django.views.decorators.csrf import csrf_exempt
|
|
6
6
|
from django.utils.decorators import method_decorator
|
|
7
7
|
|
|
8
|
-
from .webhooks import PaymeWebhook, ClickWebhook
|
|
8
|
+
from .webhooks import PaymeWebhook, ClickWebhook, AtmosWebhook
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -98,3 +98,39 @@ class BaseClickWebhookView(ClickWebhook):
|
|
|
98
98
|
transaction: Transaction object
|
|
99
99
|
"""
|
|
100
100
|
logger.info(f"Click payment cancelled: {transaction.transaction_id}")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@method_decorator(csrf_exempt, name='dispatch')
|
|
104
|
+
class BaseAtmosWebhookView(AtmosWebhook):
|
|
105
|
+
"""
|
|
106
|
+
Default Atmos webhook view.
|
|
107
|
+
|
|
108
|
+
This view handles webhook requests from the Atmos payment system.
|
|
109
|
+
You can extend this class and override the event methods to customize
|
|
110
|
+
the behavior.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
```python
|
|
114
|
+
from paytechuz.integrations.django.views import AtmosWebhookView
|
|
115
|
+
|
|
116
|
+
class CustomAtmosWebhookView(AtmosWebhookView):
|
|
117
|
+
def successfully_payment(self, params, transaction):
|
|
118
|
+
# Your custom logic here
|
|
119
|
+
print(f"Payment successful: {transaction.transaction_id}")
|
|
120
|
+
|
|
121
|
+
# Update your order status
|
|
122
|
+
order = Order.objects.get(id=transaction.account_id)
|
|
123
|
+
order.status = 'paid'
|
|
124
|
+
order.save()
|
|
125
|
+
```
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def successfully_payment(self, params, transaction):
|
|
129
|
+
"""
|
|
130
|
+
Called when a payment is successful.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
params: Request parameters
|
|
134
|
+
transaction: Transaction object
|
|
135
|
+
"""
|
|
136
|
+
logger.info(f"Atmos payment successful: {transaction.transaction_id}")
|
|
@@ -898,3 +898,134 @@ class ClickWebhook(View):
|
|
|
898
898
|
transaction: Transaction object
|
|
899
899
|
"""
|
|
900
900
|
# This method is meant to be overridden by subclasses
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
class AtmosWebhook(View):
|
|
904
|
+
"""
|
|
905
|
+
Base Atmos webhook handler for Django.
|
|
906
|
+
|
|
907
|
+
This class handles webhook requests from the Atmos payment system.
|
|
908
|
+
You can extend this class and override the event methods to customize
|
|
909
|
+
the behavior.
|
|
910
|
+
"""
|
|
911
|
+
|
|
912
|
+
def __init__(self, **kwargs):
|
|
913
|
+
super().__init__(**kwargs)
|
|
914
|
+
|
|
915
|
+
paytechuz_settings = getattr(settings, 'PAYTECHUZ', {})
|
|
916
|
+
atmos_settings = paytechuz_settings.get('ATMOS', {})
|
|
917
|
+
|
|
918
|
+
self.api_key = (
|
|
919
|
+
atmos_settings.get('API_KEY') or
|
|
920
|
+
getattr(settings, 'ATMOS_API_KEY', '')
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
account_model_path = (
|
|
924
|
+
atmos_settings.get('ACCOUNT_MODEL') or
|
|
925
|
+
getattr(settings, 'ATMOS_ACCOUNT_MODEL', 'django.contrib.auth.models.User')
|
|
926
|
+
)
|
|
927
|
+
try:
|
|
928
|
+
self.account_model = import_string(account_model_path)
|
|
929
|
+
except ImportError:
|
|
930
|
+
logger.error(
|
|
931
|
+
"Could not import %s. Check PAYTECHUZ.ATMOS.ACCOUNT_MODEL setting.",
|
|
932
|
+
account_model_path
|
|
933
|
+
)
|
|
934
|
+
raise ImportError(f"Import error: {account_model_path}") from None
|
|
935
|
+
|
|
936
|
+
self.account_field = (
|
|
937
|
+
atmos_settings.get('ACCOUNT_FIELD') or
|
|
938
|
+
getattr(settings, 'ATMOS_ACCOUNT_FIELD', 'id')
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
def post(self, request, **_):
|
|
942
|
+
"""
|
|
943
|
+
Handle POST requests from Atmos.
|
|
944
|
+
"""
|
|
945
|
+
try:
|
|
946
|
+
# Parse request data
|
|
947
|
+
data = json.loads(request.body.decode('utf-8'))
|
|
948
|
+
|
|
949
|
+
# Verify signature
|
|
950
|
+
received_signature = data.get('sign', '')
|
|
951
|
+
if not self._verify_signature(data, received_signature):
|
|
952
|
+
logger.error("Invalid webhook signature")
|
|
953
|
+
return JsonResponse({
|
|
954
|
+
'status': 0,
|
|
955
|
+
'message': 'Invalid signature'
|
|
956
|
+
}, status=400)
|
|
957
|
+
|
|
958
|
+
# Extract webhook data
|
|
959
|
+
store_id = data.get('store_id')
|
|
960
|
+
transaction_id = data.get('transaction_id')
|
|
961
|
+
amount = data.get('amount')
|
|
962
|
+
invoice = data.get('invoice')
|
|
963
|
+
transaction_time = data.get('transaction_time')
|
|
964
|
+
|
|
965
|
+
logger.info(f"Webhook received for transaction {transaction_id}, "
|
|
966
|
+
f"invoice {invoice}, amount {amount}")
|
|
967
|
+
|
|
968
|
+
# Find transaction by invoice (account)
|
|
969
|
+
try:
|
|
970
|
+
transaction = PaymentTransaction._default_manager.get(
|
|
971
|
+
gateway=PaymentTransaction.ATMOS,
|
|
972
|
+
account_id=invoice
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
# Update transaction with webhook data
|
|
976
|
+
transaction.transaction_id = transaction_id
|
|
977
|
+
transaction.mark_as_paid()
|
|
978
|
+
|
|
979
|
+
# Call the event method
|
|
980
|
+
self.successfully_payment(data, transaction)
|
|
981
|
+
|
|
982
|
+
return JsonResponse({
|
|
983
|
+
'status': 1,
|
|
984
|
+
'message': 'Успешно'
|
|
985
|
+
})
|
|
986
|
+
|
|
987
|
+
except PaymentTransaction.DoesNotExist:
|
|
988
|
+
logger.error(f"Transaction not found for invoice: {invoice}")
|
|
989
|
+
return JsonResponse({
|
|
990
|
+
'status': 0,
|
|
991
|
+
'message': f'Transaction not found for invoice: {invoice}'
|
|
992
|
+
}, status=400)
|
|
993
|
+
|
|
994
|
+
except Exception as e:
|
|
995
|
+
logger.exception("Unexpected error in Atmos webhook: %s", e)
|
|
996
|
+
return JsonResponse({
|
|
997
|
+
'status': 0,
|
|
998
|
+
'message': f'Error: {str(e)}'
|
|
999
|
+
}, status=500)
|
|
1000
|
+
|
|
1001
|
+
def _verify_signature(self, webhook_data, received_signature):
|
|
1002
|
+
"""
|
|
1003
|
+
Verify webhook signature from Atmos.
|
|
1004
|
+
"""
|
|
1005
|
+
# Extract data from webhook
|
|
1006
|
+
store_id = str(webhook_data.get('store_id', ''))
|
|
1007
|
+
transaction_id = str(webhook_data.get('transaction_id', ''))
|
|
1008
|
+
invoice = str(webhook_data.get('invoice', ''))
|
|
1009
|
+
amount = str(webhook_data.get('amount', ''))
|
|
1010
|
+
|
|
1011
|
+
# Create signature string: store_id+transaction_id+invoice+amount+api_key
|
|
1012
|
+
signature_string = f"{store_id}{transaction_id}{invoice}{amount}{self.api_key}"
|
|
1013
|
+
|
|
1014
|
+
# Generate MD5 hash
|
|
1015
|
+
calculated_signature = hashlib.md5(
|
|
1016
|
+
signature_string.encode('utf-8')).hexdigest()
|
|
1017
|
+
|
|
1018
|
+
# Compare signatures
|
|
1019
|
+
return calculated_signature == received_signature
|
|
1020
|
+
|
|
1021
|
+
# Event methods that can be overridden by subclasses
|
|
1022
|
+
|
|
1023
|
+
def successfully_payment(self, params, transaction):
|
|
1024
|
+
"""
|
|
1025
|
+
Called when a payment is successful.
|
|
1026
|
+
|
|
1027
|
+
Args:
|
|
1028
|
+
params: Request parameters
|
|
1029
|
+
transaction: Transaction object
|
|
1030
|
+
"""
|
|
1031
|
+
# This method is meant to be overridden by subclasses
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paytechuz
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Unified Python package for Uzbekistan payment gateways
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Unified Python package for Uzbekistan payment gateways (Payme, Click, Atmos)
|
|
5
5
|
Home-page: https://github.com/Muhammadali-Akbarov/paytechuz
|
|
6
6
|
Author: Muhammadali Akbarov
|
|
7
7
|
Author-email: muhammadali17abc@gmail.com
|
|
@@ -20,7 +20,7 @@ Dynamic: requires-python
|
|
|
20
20
|
[](https://pay-tech.uz)
|
|
21
21
|
[](https://opensource.org/licenses/MIT)
|
|
22
22
|
|
|
23
|
-
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
|
|
23
|
+
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, Click, and Atmos payment gateways.
|
|
24
24
|
|
|
25
25
|
📖 **[Complete Documentation](https://pay-tech.uz)** | 🚀 **[Quick Start Guide](https://pay-tech.uz/quickstart)**
|
|
26
26
|
|
|
@@ -59,6 +59,7 @@ pip install paytechuz[fastapi]
|
|
|
59
59
|
```python
|
|
60
60
|
from paytechuz.gateways.payme import PaymeGateway
|
|
61
61
|
from paytechuz.gateways.click import ClickGateway
|
|
62
|
+
from paytechuz.gateways.atmos import AtmosGateway
|
|
62
63
|
|
|
63
64
|
# Initialize Payme gateway
|
|
64
65
|
payme = PaymeGateway(
|
|
@@ -76,6 +77,15 @@ click = ClickGateway(
|
|
|
76
77
|
is_test_mode=True # Set to False in production environment
|
|
77
78
|
)
|
|
78
79
|
|
|
80
|
+
# Initialize Atmos gateway
|
|
81
|
+
atmos = AtmosGateway(
|
|
82
|
+
consumer_key="your_consumer_key",
|
|
83
|
+
consumer_secret="your_consumer_secret",
|
|
84
|
+
store_id="your_store_id",
|
|
85
|
+
terminal_id="your_terminal_id", # optional
|
|
86
|
+
is_test_mode=True # Set to False in production environment
|
|
87
|
+
)
|
|
88
|
+
|
|
79
89
|
# Generate payment links
|
|
80
90
|
payme_link = payme.create_payment(
|
|
81
91
|
id="order_123",
|
|
@@ -89,6 +99,116 @@ click_link = click.create_payment(
|
|
|
89
99
|
description="Test payment",
|
|
90
100
|
return_url="https://example.com/return"
|
|
91
101
|
)
|
|
102
|
+
|
|
103
|
+
atmos_payment = atmos.create_payment(
|
|
104
|
+
account_id="order_123",
|
|
105
|
+
amount=150000 # amount in UZS
|
|
106
|
+
)
|
|
107
|
+
atmos_link = atmos_payment['payment_url']
|
|
108
|
+
|
|
109
|
+
# Check payment status
|
|
110
|
+
status = atmos.check_payment(atmos_payment['transaction_id'])
|
|
111
|
+
print(f"Payment status: {status['status']}")
|
|
112
|
+
|
|
113
|
+
# Cancel payment if needed
|
|
114
|
+
if status['status'] == 'pending':
|
|
115
|
+
cancel_result = atmos.cancel_payment(
|
|
116
|
+
transaction_id=atmos_payment['transaction_id'],
|
|
117
|
+
reason="Customer request"
|
|
118
|
+
)
|
|
119
|
+
print(f"Cancellation status: {cancel_result['status']}")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Complete Atmos Integration Example
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from paytechuz.gateways.atmos import AtmosGateway
|
|
126
|
+
from paytechuz.gateways.atmos.webhook import AtmosWebhookHandler
|
|
127
|
+
|
|
128
|
+
# 1. Initialize Atmos Gateway
|
|
129
|
+
atmos = AtmosGateway(
|
|
130
|
+
consumer_key="your_consumer_key",
|
|
131
|
+
consumer_secret="your_consumer_secret",
|
|
132
|
+
store_id="your_store_id",
|
|
133
|
+
terminal_id="your_terminal_id", # optional
|
|
134
|
+
is_test_mode=True
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# 2. Create payment
|
|
138
|
+
def create_atmos_payment(order_id, amount):
|
|
139
|
+
try:
|
|
140
|
+
payment = atmos.create_payment(
|
|
141
|
+
account_id=order_id,
|
|
142
|
+
amount=amount
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
print(f"✅ Payment created successfully!")
|
|
146
|
+
print(f"Transaction ID: {payment['transaction_id']}")
|
|
147
|
+
print(f"Payment URL: {payment['payment_url']}")
|
|
148
|
+
print(f"Redirect user to: {payment['payment_url']}")
|
|
149
|
+
|
|
150
|
+
return payment
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f"❌ Payment creation failed: {e}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
# 3. Check payment status
|
|
157
|
+
def check_atmos_payment(transaction_id):
|
|
158
|
+
try:
|
|
159
|
+
status = atmos.check_payment(transaction_id)
|
|
160
|
+
|
|
161
|
+
print(f"Transaction ID: {status['transaction_id']}")
|
|
162
|
+
print(f"Status: {status['status']}")
|
|
163
|
+
|
|
164
|
+
return status
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
print(f"❌ Status check failed: {e}")
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
# 4. Handle webhook (for web frameworks)
|
|
171
|
+
def handle_atmos_webhook(webhook_data):
|
|
172
|
+
webhook_handler = AtmosWebhookHandler(api_key="your_atmos_api_key")
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
response = webhook_handler.handle_webhook(webhook_data)
|
|
176
|
+
|
|
177
|
+
if response['status'] == 1:
|
|
178
|
+
# Payment successful
|
|
179
|
+
transaction_id = webhook_data.get('transaction_id')
|
|
180
|
+
amount = webhook_data.get('amount')
|
|
181
|
+
invoice = webhook_data.get('invoice')
|
|
182
|
+
|
|
183
|
+
print(f"✅ Payment successful!")
|
|
184
|
+
print(f"Transaction ID: {transaction_id}")
|
|
185
|
+
print(f"Order ID: {invoice}")
|
|
186
|
+
print(f"Amount: {amount}")
|
|
187
|
+
|
|
188
|
+
# Update your order status here
|
|
189
|
+
# update_order_status(invoice, 'paid')
|
|
190
|
+
|
|
191
|
+
return response
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
print(f"❌ Webhook processing failed: {e}")
|
|
195
|
+
return {
|
|
196
|
+
'status': 0,
|
|
197
|
+
'message': f'Error: {str(e)}'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Usage example
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
# Create payment
|
|
203
|
+
payment = create_atmos_payment("order_12345", 50000) # 500.00 UZS
|
|
204
|
+
|
|
205
|
+
if payment:
|
|
206
|
+
# Check status after some time
|
|
207
|
+
import time
|
|
208
|
+
time.sleep(5) # Wait 5 seconds
|
|
209
|
+
|
|
210
|
+
status = check_atmos_payment(payment['transaction_id'])
|
|
211
|
+
print(f"Current status: {status['status'] if status else 'Unknown'}")
|
|
92
212
|
```
|
|
93
213
|
|
|
94
214
|
### Django Integration
|
|
@@ -144,6 +264,16 @@ PAYTECHUZ = {
|
|
|
144
264
|
'ACCOUNT_MODEL': 'your_app.models.Order',
|
|
145
265
|
'COMMISSION_PERCENT': 0.0,
|
|
146
266
|
'IS_TEST_MODE': True, # Set to False in production
|
|
267
|
+
},
|
|
268
|
+
'ATMOS': {
|
|
269
|
+
'CONSUMER_KEY': 'your_atmos_consumer_key',
|
|
270
|
+
'CONSUMER_SECRET': 'your_atmos_consumer_secret',
|
|
271
|
+
'STORE_ID': 'your_atmos_store_id',
|
|
272
|
+
'TERMINAL_ID': 'your_atmos_terminal_id', # Optional
|
|
273
|
+
'API_KEY': 'your_atmos_api_key'
|
|
274
|
+
'ACCOUNT_MODEL': 'your_app.models.Order',
|
|
275
|
+
'ACCOUNT_FIELD': 'id',
|
|
276
|
+
'IS_TEST_MODE': True, # Set to False in production
|
|
147
277
|
}
|
|
148
278
|
}
|
|
149
279
|
```
|
|
@@ -152,7 +282,11 @@ PAYTECHUZ = {
|
|
|
152
282
|
|
|
153
283
|
```python
|
|
154
284
|
# views.py
|
|
155
|
-
from paytechuz.integrations.django.views import
|
|
285
|
+
from paytechuz.integrations.django.views import (
|
|
286
|
+
BasePaymeWebhookView,
|
|
287
|
+
BaseClickWebhookView,
|
|
288
|
+
BaseAtmosWebhookView
|
|
289
|
+
)
|
|
156
290
|
from .models import Order
|
|
157
291
|
|
|
158
292
|
class PaymeWebhookView(BasePaymeWebhookView):
|
|
@@ -176,6 +310,17 @@ class ClickWebhookView(BaseClickWebhookView):
|
|
|
176
310
|
order = Order.objects.get(id=transaction.account_id)
|
|
177
311
|
order.status = 'cancelled'
|
|
178
312
|
order.save()
|
|
313
|
+
|
|
314
|
+
class AtmosWebhookView(BaseAtmosWebhookView):
|
|
315
|
+
def successfully_payment(self, params, transaction):
|
|
316
|
+
order = Order.objects.get(id=transaction.account_id)
|
|
317
|
+
order.status = 'paid'
|
|
318
|
+
order.save()
|
|
319
|
+
|
|
320
|
+
def cancelled_payment(self, params, transaction):
|
|
321
|
+
order = Order.objects.get(id=transaction.account_id)
|
|
322
|
+
order.status = 'cancelled'
|
|
323
|
+
order.save()
|
|
179
324
|
```
|
|
180
325
|
|
|
181
326
|
4. Add webhook URLs to `urls.py`:
|
|
@@ -184,12 +329,13 @@ class ClickWebhookView(BaseClickWebhookView):
|
|
|
184
329
|
# urls.py
|
|
185
330
|
from django.urls import path
|
|
186
331
|
from django.views.decorators.csrf import csrf_exempt
|
|
187
|
-
from .views import PaymeWebhookView, ClickWebhookView
|
|
332
|
+
from .views import PaymeWebhookView, ClickWebhookView, AtmosWebhookView
|
|
188
333
|
|
|
189
334
|
urlpatterns = [
|
|
190
335
|
# ...
|
|
191
336
|
path('payments/webhook/payme/', csrf_exempt(PaymeWebhookView.as_view()), name='payme_webhook'),
|
|
192
337
|
path('payments/webhook/click/', csrf_exempt(ClickWebhookView.as_view()), name='click_webhook'),
|
|
338
|
+
path('payments/webhook/atmos/', csrf_exempt(AtmosWebhookView.as_view()), name='atmos_webhook'),
|
|
193
339
|
]
|
|
194
340
|
```
|
|
195
341
|
|
|
@@ -243,6 +389,7 @@ from fastapi import FastAPI, Request, Depends
|
|
|
243
389
|
from sqlalchemy.orm import Session
|
|
244
390
|
|
|
245
391
|
from paytechuz.integrations.fastapi import PaymeWebhookHandler, ClickWebhookHandler
|
|
392
|
+
from paytechuz.gateways.atmos.webhook import AtmosWebhookHandler
|
|
246
393
|
|
|
247
394
|
|
|
248
395
|
app = FastAPI()
|
|
@@ -302,6 +449,39 @@ async def click_webhook(request: Request, db: Session = Depends(get_db)):
|
|
|
302
449
|
account_model=Order
|
|
303
450
|
)
|
|
304
451
|
return await handler.handle_webhook(request)
|
|
452
|
+
|
|
453
|
+
@app.post("/payments/atmos/webhook")
|
|
454
|
+
async def atmos_webhook(request: Request, db: Session = Depends(get_db)):
|
|
455
|
+
import json
|
|
456
|
+
|
|
457
|
+
# Atmos webhook handler
|
|
458
|
+
atmos_handler = AtmosWebhookHandler(api_key="your_atmos_api_key")
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
# Get request body
|
|
462
|
+
body = await request.body()
|
|
463
|
+
webhook_data = json.loads(body.decode('utf-8'))
|
|
464
|
+
|
|
465
|
+
# Process webhook
|
|
466
|
+
response = atmos_handler.handle_webhook(webhook_data)
|
|
467
|
+
|
|
468
|
+
if response['status'] == 1:
|
|
469
|
+
# Payment successful
|
|
470
|
+
invoice = webhook_data.get('invoice')
|
|
471
|
+
|
|
472
|
+
# Update order status
|
|
473
|
+
order = db.query(Order).filter(Order.id == invoice).first()
|
|
474
|
+
if order:
|
|
475
|
+
order.status = "paid"
|
|
476
|
+
db.commit()
|
|
477
|
+
|
|
478
|
+
return response
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
return {
|
|
482
|
+
'status': 0,
|
|
483
|
+
'message': f'Error: {str(e)}'
|
|
484
|
+
}
|
|
305
485
|
```
|
|
306
486
|
|
|
307
487
|
## Documentation
|
|
@@ -315,11 +495,13 @@ Detailed documentation is available in multiple languages:
|
|
|
315
495
|
|
|
316
496
|
- [Django Integration Guide](src/docs/en/django_integration.md) | [Django integratsiyasi bo'yicha qo'llanma](src/docs/django_integration.md)
|
|
317
497
|
- [FastAPI Integration Guide](src/docs/en/fastapi_integration.md) | [FastAPI integratsiyasi bo'yicha qo'llanma](src/docs/fastapi_integration.md)
|
|
498
|
+
- [Atmos Integration Guide](src/docs/en/atmos_integration.md) | [Atmos integratsiyasi bo'yicha qo'llanma](src/docs/atmos_integration.md)
|
|
318
499
|
|
|
319
500
|
## Supported Payment Systems
|
|
320
501
|
|
|
321
502
|
- **Payme** - [Official Website](https://payme.uz)
|
|
322
503
|
- **Click** - [Official Website](https://click.uz)
|
|
504
|
+
- **Atmos** - [Official Website](https://atmos.uz)
|
|
323
505
|
|
|
324
506
|
## Contributing
|
|
325
507
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
paytechuz/__init__.py,sha256=
|
|
1
|
+
paytechuz/__init__.py,sha256=Pfndm1UE44F1lgRs9wFP3OH0760ziw3EBJbN-wkRuXg,1939
|
|
2
2
|
paytechuz/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
paytechuz/core/base.py,sha256=Es6eEGNgDjQJe-kEJVAHSAh8AWbgtIuQMm0xn7qfjl4,2549
|
|
4
|
-
paytechuz/core/constants.py,sha256=
|
|
4
|
+
paytechuz/core/constants.py,sha256=hzCy5Kc8HVSDhXxNgObbJ2e9SYcXTYL3qky_q0tlpHU,2305
|
|
5
5
|
paytechuz/core/exceptions.py,sha256=XMJkqiponTkvhjoh3S2iFNuU3UbBdFW4130kd0hpudg,5489
|
|
6
6
|
paytechuz/core/http.py,sha256=1PFv_Fo62GtfyYKUK2nsT4AaeQNuMgZlFUFL1q9p2MI,7672
|
|
7
7
|
paytechuz/core/utils.py,sha256=EbNtDweR1ABOtCu4D6cYlolM0t_fbiE3gNoc_qfcKKA,4704
|
|
8
8
|
paytechuz/core/payme/errors.py,sha256=CZE62MbYDMsRfNIX23Syt6of_tPMMGLnXhYMii4hw3A,542
|
|
9
9
|
paytechuz/gateways/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
paytechuz/gateways/atmos/__init__.py,sha256=piVcHu32K-D8I0Kr6bSXXrdtny2S5TY0hxMbyCuVuIo,91
|
|
11
|
+
paytechuz/gateways/atmos/client.py,sha256=Llo4utBVYPq1a3eLQaCtOMIzXooRhati3Eh2o6qbVdE,6212
|
|
12
|
+
paytechuz/gateways/atmos/webhook.py,sha256=bqMHipJ_GowGYuxVaZd2f8bSEzSNHavdsvpGpsqy96s,2989
|
|
10
13
|
paytechuz/gateways/click/__init__.py,sha256=35RPIrZYHgMWDzxjQkJMZYjzHDa8cY_BqQztCdZZmBM,90
|
|
11
14
|
paytechuz/gateways/click/client.py,sha256=NwsPGfXacO0tWvZCA0V9KZ9XhFV7AnyHBmtxsWAvci8,6736
|
|
12
15
|
paytechuz/gateways/click/merchant.py,sha256=tvHUwNr_eiDz_ED4-m2GNBh_LXN0b5lwtq1jw1e0zAQ,7191
|
|
@@ -20,17 +23,18 @@ paytechuz/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
20
23
|
paytechuz/integrations/django/__init__.py,sha256=fNs4c2IWpCe78-_Yvgz59TdKbHiYRYDkLR33QOBf-Ok,356
|
|
21
24
|
paytechuz/integrations/django/admin.py,sha256=6fs6GiKcdc-hGlLxJ0BthY7TFo_2RVVJRhQwhxMroCY,2664
|
|
22
25
|
paytechuz/integrations/django/apps.py,sha256=Q9wG2osL7_Ip2BcAkq7lmmhu4UKJAg6UtSsSq_RgHlc,640
|
|
23
|
-
paytechuz/integrations/django/models.py,sha256=
|
|
26
|
+
paytechuz/integrations/django/models.py,sha256=EInAti6LZlf7t1kiTSceV-xQZ-hNcVe8qzem2yjOWh4,5742
|
|
24
27
|
paytechuz/integrations/django/signals.py,sha256=VtNYEAnu13wi9PqadEaCU9LY_k2tY26AS4bnPIAqw7M,1319
|
|
25
|
-
paytechuz/integrations/django/views.py,sha256=
|
|
26
|
-
paytechuz/integrations/django/webhooks.py,sha256=
|
|
28
|
+
paytechuz/integrations/django/views.py,sha256=KFiuMcr4BtMbzbrW_RbIkv0-Fk214WIWWwaStscArzs,4149
|
|
29
|
+
paytechuz/integrations/django/webhooks.py,sha256=j5f4BbOQaFniK26RTQW7KlM90KFOioCUwjBjJQCdkek,36646
|
|
27
30
|
paytechuz/integrations/django/migrations/0001_initial.py,sha256=SWHIUuwq91crzaxa9v1UK0kay8CxsjUo6t4bqg7j0Gw,1896
|
|
31
|
+
paytechuz/integrations/django/migrations/0002_alter_paymenttransaction_gateway.py,sha256=XiZx5urgfhXxra3W_KWksQ1LbaDOs3sjPn4w0T2cW50,457
|
|
28
32
|
paytechuz/integrations/django/migrations/__init__.py,sha256=KLQ5NdjOMLDS21-u3b_g08G1MjPMMhG95XI_N8m4FSo,41
|
|
29
33
|
paytechuz/integrations/fastapi/__init__.py,sha256=DLnhAZQZf2ghu8BuFFfE7FzbNKWQQ2SLG8qxldRuwR4,565
|
|
30
34
|
paytechuz/integrations/fastapi/models.py,sha256=9IqrsndIVuIDwDbijZ89biJxEWQASXRBfWVShxgerAc,5113
|
|
31
35
|
paytechuz/integrations/fastapi/routes.py,sha256=t8zbqhMZsaJmEvMDgmF-NoRmbqksfX_AvIrx-3kCjg8,37845
|
|
32
36
|
paytechuz/integrations/fastapi/schemas.py,sha256=PgRqviJiD4-u3_CIkUOX8R7L8Yqn8L44WLte7968G0E,3887
|
|
33
|
-
paytechuz-0.
|
|
34
|
-
paytechuz-0.
|
|
35
|
-
paytechuz-0.
|
|
36
|
-
paytechuz-0.
|
|
37
|
+
paytechuz-0.3.0.dist-info/METADATA,sha256=UsVUiMRztSPWOfzb6N3GthBY7ZdMhBBbdsSSAwp_4QQ,15730
|
|
38
|
+
paytechuz-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
paytechuz-0.3.0.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
|
|
40
|
+
paytechuz-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|