paytechuz 0.2.24__py3-none-any.whl → 0.3.1__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 CHANGED
@@ -1,12 +1,11 @@
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 Click
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
- from typing import Any
8
7
 
9
- __version__ = '0.2.24'
8
+ __version__ = '0.3.1'
10
9
 
11
10
  # Import framework integrations - these imports are used to check availability
12
11
  # of frameworks, not for direct usage
@@ -31,6 +30,7 @@ except ImportError:
31
30
  from paytechuz.core.base import BasePaymentGateway # noqa: E402
32
31
  from paytechuz.gateways.payme.client import PaymeGateway # noqa: E402
33
32
  from paytechuz.gateways.click.client import ClickGateway # noqa: E402
33
+ from paytechuz.gateways.atmos.client import AtmosGateway # noqa: E402
34
34
  from paytechuz.core.constants import PaymentGateway # noqa: E402
35
35
 
36
36
 
@@ -39,7 +39,7 @@ def create_gateway(gateway_type: str, **kwargs) -> BasePaymentGateway:
39
39
  Create a payment gateway instance.
40
40
 
41
41
  Args:
42
- gateway_type: Type of gateway ('payme' or 'click')
42
+ gateway_type: Type of gateway ('payme', 'click', or 'atmos')
43
43
  **kwargs: Gateway-specific configuration
44
44
 
45
45
  Returns:
@@ -53,5 +53,7 @@ def create_gateway(gateway_type: str, **kwargs) -> BasePaymentGateway:
53
53
  return PaymeGateway(**kwargs)
54
54
  if gateway_type.lower() == PaymentGateway.CLICK.value:
55
55
  return ClickGateway(**kwargs)
56
+ if gateway_type.lower() == PaymentGateway.ATMOS.value:
57
+ return AtmosGateway(**kwargs)
56
58
 
57
59
  raise ValueError(f"Unsupported gateway type: {gateway_type}")
@@ -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,2 @@
1
+ """Atmos payment gateway implementation."""
2
+ from .client import AtmosGateway # noqa: F401
@@ -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.2.24
4
- Summary: Unified Python package for Uzbekistan payment gateways
3
+ Version: 0.3.1
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
  [![Documentation](https://img.shields.io/badge/docs-pay--tech.uz-blue.svg)](https://pay-tech.uz)
21
21
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 Click payment gateways.
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,24 @@ 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']}")
92
120
  ```
93
121
 
94
122
  ### Django Integration
@@ -144,6 +172,16 @@ PAYTECHUZ = {
144
172
  'ACCOUNT_MODEL': 'your_app.models.Order',
145
173
  'COMMISSION_PERCENT': 0.0,
146
174
  'IS_TEST_MODE': True, # Set to False in production
175
+ },
176
+ 'ATMOS': {
177
+ 'CONSUMER_KEY': 'your_atmos_consumer_key',
178
+ 'CONSUMER_SECRET': 'your_atmos_consumer_secret',
179
+ 'STORE_ID': 'your_atmos_store_id',
180
+ 'TERMINAL_ID': 'your_atmos_terminal_id', # Optional
181
+ 'API_KEY': 'your_atmos_api_key'
182
+ 'ACCOUNT_MODEL': 'your_app.models.Order',
183
+ 'ACCOUNT_FIELD': 'id',
184
+ 'IS_TEST_MODE': True, # Set to False in production
147
185
  }
148
186
  }
149
187
  ```
@@ -152,7 +190,11 @@ PAYTECHUZ = {
152
190
 
153
191
  ```python
154
192
  # views.py
155
- from paytechuz.integrations.django.views import BasePaymeWebhookView, BaseClickWebhookView
193
+ from paytechuz.integrations.django.views import (
194
+ BasePaymeWebhookView,
195
+ BaseClickWebhookView,
196
+ BaseAtmosWebhookView
197
+ )
156
198
  from .models import Order
157
199
 
158
200
  class PaymeWebhookView(BasePaymeWebhookView):
@@ -176,6 +218,17 @@ class ClickWebhookView(BaseClickWebhookView):
176
218
  order = Order.objects.get(id=transaction.account_id)
177
219
  order.status = 'cancelled'
178
220
  order.save()
221
+
222
+ class AtmosWebhookView(BaseAtmosWebhookView):
223
+ def successfully_payment(self, params, transaction):
224
+ order = Order.objects.get(id=transaction.account_id)
225
+ order.status = 'paid'
226
+ order.save()
227
+
228
+ def cancelled_payment(self, params, transaction):
229
+ order = Order.objects.get(id=transaction.account_id)
230
+ order.status = 'cancelled'
231
+ order.save()
179
232
  ```
180
233
 
181
234
  4. Add webhook URLs to `urls.py`:
@@ -184,12 +237,13 @@ class ClickWebhookView(BaseClickWebhookView):
184
237
  # urls.py
185
238
  from django.urls import path
186
239
  from django.views.decorators.csrf import csrf_exempt
187
- from .views import PaymeWebhookView, ClickWebhookView
240
+ from .views import PaymeWebhookView, ClickWebhookView, AtmosWebhookView
188
241
 
189
242
  urlpatterns = [
190
243
  # ...
191
244
  path('payments/webhook/payme/', csrf_exempt(PaymeWebhookView.as_view()), name='payme_webhook'),
192
245
  path('payments/webhook/click/', csrf_exempt(ClickWebhookView.as_view()), name='click_webhook'),
246
+ path('payments/webhook/atmos/', csrf_exempt(AtmosWebhookView.as_view()), name='atmos_webhook'),
193
247
  ]
194
248
  ```
195
249
 
@@ -243,6 +297,7 @@ from fastapi import FastAPI, Request, Depends
243
297
  from sqlalchemy.orm import Session
244
298
 
245
299
  from paytechuz.integrations.fastapi import PaymeWebhookHandler, ClickWebhookHandler
300
+ from paytechuz.gateways.atmos.webhook import AtmosWebhookHandler
246
301
 
247
302
 
248
303
  app = FastAPI()
@@ -302,6 +357,39 @@ async def click_webhook(request: Request, db: Session = Depends(get_db)):
302
357
  account_model=Order
303
358
  )
304
359
  return await handler.handle_webhook(request)
360
+
361
+ @app.post("/payments/atmos/webhook")
362
+ async def atmos_webhook(request: Request, db: Session = Depends(get_db)):
363
+ import json
364
+
365
+ # Atmos webhook handler
366
+ atmos_handler = AtmosWebhookHandler(api_key="your_atmos_api_key")
367
+
368
+ try:
369
+ # Get request body
370
+ body = await request.body()
371
+ webhook_data = json.loads(body.decode('utf-8'))
372
+
373
+ # Process webhook
374
+ response = atmos_handler.handle_webhook(webhook_data)
375
+
376
+ if response['status'] == 1:
377
+ # Payment successful
378
+ invoice = webhook_data.get('invoice')
379
+
380
+ # Update order status
381
+ order = db.query(Order).filter(Order.id == invoice).first()
382
+ if order:
383
+ order.status = "paid"
384
+ db.commit()
385
+
386
+ return response
387
+
388
+ except Exception as e:
389
+ return {
390
+ 'status': 0,
391
+ 'message': f'Error: {str(e)}'
392
+ }
305
393
  ```
306
394
 
307
395
  ## Documentation
@@ -315,11 +403,13 @@ Detailed documentation is available in multiple languages:
315
403
 
316
404
  - [Django Integration Guide](src/docs/en/django_integration.md) | [Django integratsiyasi bo'yicha qo'llanma](src/docs/django_integration.md)
317
405
  - [FastAPI Integration Guide](src/docs/en/fastapi_integration.md) | [FastAPI integratsiyasi bo'yicha qo'llanma](src/docs/fastapi_integration.md)
406
+ - [Atmos Integration Guide](src/docs/en/atmos_integration.md) | [Atmos integratsiyasi bo'yicha qo'llanma](src/docs/atmos_integration.md)
318
407
 
319
408
  ## Supported Payment Systems
320
409
 
321
410
  - **Payme** - [Official Website](https://payme.uz)
322
411
  - **Click** - [Official Website](https://click.uz)
412
+ - **Atmos** - [Official Website](https://atmos.uz)
323
413
 
324
414
  ## Contributing
325
415
 
@@ -1,12 +1,15 @@
1
- paytechuz/__init__.py,sha256=ODKIHhEIrUJ9I-8OQ22jNkG0W8PCHxWi3DBFXFQLqYk,1754
1
+ paytechuz/__init__.py,sha256=te3Ao8uvuOI6bl9Uq1HsNfmztVZ9c_uvqi3gdODFkEE,1916
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=P2zeZ_cfZIttdC1vqkpIngkfRFh6loWzJYEgzQb5cKA,1660
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=x3cVLY812Xts5oNk4VmCzK3zjb0FXQON9WV41PCtxaw,5696
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=TL-LNbwrLvYjoBvGxm_yZjMVLBqelorgKjr4l3sKH1Y,3037
26
- paytechuz/integrations/django/webhooks.py,sha256=gATM5oxoNKznnCHHYanjwhgdpeoRGIhDGRxVO3SIYYU,32048
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.2.24.dist-info/METADATA,sha256=wWTY1mR8E2eE9ChZjydxSYQoITVmPwWpjqV0sCiMH_8,10084
34
- paytechuz-0.2.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
- paytechuz-0.2.24.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
36
- paytechuz-0.2.24.dist-info/RECORD,,
37
+ paytechuz-0.3.1.dist-info/METADATA,sha256=5mtAMGPdOCQ9Tophk_QBPcADUCC2Pjtv5wegmPk_5JM,13096
38
+ paytechuz-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ paytechuz-0.3.1.dist-info/top_level.txt,sha256=oloyKGNVj9Z2h3wpKG5yPyTlpdpWW0-CWr-j-asCWBc,10
40
+ paytechuz-0.3.1.dist-info/RECORD,,