django-cfg 1.2.23__py3-none-any.whl → 1.2.25__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
- django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
- django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
- django_cfg/apps/payments/config/__init__.py +15 -37
- django_cfg/apps/payments/config/module.py +30 -122
- django_cfg/apps/payments/config/providers.py +22 -0
- django_cfg/apps/payments/config/settings.py +53 -93
- django_cfg/apps/payments/config/utils.py +10 -156
- django_cfg/apps/payments/management/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/README.md +178 -0
- django_cfg/apps/payments/management/commands/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
- django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
- django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
- django_cfg/apps/payments/managers/currency_manager.py +65 -14
- django_cfg/apps/payments/middleware/api_access.py +33 -0
- django_cfg/apps/payments/migrations/0001_initial.py +94 -1
- django_cfg/apps/payments/models/payments.py +110 -0
- django_cfg/apps/payments/services/__init__.py +7 -1
- django_cfg/apps/payments/services/core/balance_service.py +14 -16
- django_cfg/apps/payments/services/core/fallback_service.py +432 -0
- django_cfg/apps/payments/services/core/payment_service.py +212 -29
- django_cfg/apps/payments/services/core/subscription_service.py +15 -17
- django_cfg/apps/payments/services/internal_types.py +31 -0
- django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
- django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
- django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
- django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -0
- django_cfg/apps/payments/services/security/__init__.py +34 -0
- django_cfg/apps/payments/services/security/error_handler.py +637 -0
- django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
- django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
- django_cfg/apps/payments/signals/api_key_signals.py +10 -0
- django_cfg/apps/payments/signals/payment_signals.py +3 -2
- django_cfg/apps/payments/tasks/__init__.py +12 -0
- django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
- django_cfg/apps/payments/utils/__init__.py +7 -4
- django_cfg/apps/payments/utils/billing_utils.py +342 -0
- django_cfg/apps/payments/utils/config_utils.py +2 -0
- django_cfg/apps/payments/views/payment_views.py +40 -2
- django_cfg/apps/payments/views/webhook_views.py +266 -0
- django_cfg/apps/payments/viewsets.py +65 -0
- django_cfg/cli/README.md +2 -2
- django_cfg/cli/commands/create_project.py +1 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/main.py +1 -1
- django_cfg/cli/utils.py +5 -5
- django_cfg/core/config.py +18 -4
- django_cfg/models/payments.py +546 -0
- django_cfg/models/tasks.py +51 -2
- django_cfg/modules/base.py +11 -5
- django_cfg/modules/django_currency/README.md +104 -269
- django_cfg/modules/django_currency/__init__.py +99 -41
- django_cfg/modules/django_currency/clients/__init__.py +11 -0
- django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
- django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
- django_cfg/modules/django_currency/core/__init__.py +42 -0
- django_cfg/modules/django_currency/core/converter.py +169 -0
- django_cfg/modules/django_currency/core/exceptions.py +28 -0
- django_cfg/modules/django_currency/core/models.py +54 -0
- django_cfg/modules/django_currency/database/__init__.py +25 -0
- django_cfg/modules/django_currency/database/database_loader.py +507 -0
- django_cfg/modules/django_currency/utils/__init__.py +9 -0
- django_cfg/modules/django_currency/utils/cache.py +92 -0
- django_cfg/registry/core.py +10 -0
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/METADATA +10 -6
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/RECORD +77 -51
- django_cfg/apps/agents/examples/__init__.py +0 -3
- django_cfg/apps/agents/examples/simple_example.py +0 -161
- django_cfg/apps/knowbase/examples/__init__.py +0 -3
- django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
- django_cfg/modules/django_currency/cache.py +0 -430
- django_cfg/modules/django_currency/converter.py +0 -324
- django_cfg/modules/django_currency/service.py +0 -277
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,310 @@
|
|
1
|
+
"""
|
2
|
+
Cryptomus payment provider implementation.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import hashlib
|
7
|
+
import json
|
8
|
+
import base64
|
9
|
+
from decimal import Decimal
|
10
|
+
from typing import Dict, Any, Optional
|
11
|
+
from dataclasses import dataclass
|
12
|
+
|
13
|
+
import requests
|
14
|
+
from pydantic import BaseModel, Field, validator
|
15
|
+
|
16
|
+
from .base import PaymentProvider, ProviderResponse, ProviderConfig
|
17
|
+
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
class CryptomusConfig(ProviderConfig):
|
22
|
+
"""Configuration for Cryptomus provider."""
|
23
|
+
|
24
|
+
merchant_id: str = Field(..., description="Cryptomus merchant ID")
|
25
|
+
api_key: str = Field(..., description="Cryptomus API key")
|
26
|
+
test_mode: bool = Field(default=False, description="Enable test mode")
|
27
|
+
callback_url: Optional[str] = Field(None, description="Default callback URL")
|
28
|
+
|
29
|
+
@validator('merchant_id')
|
30
|
+
def validate_merchant_id(cls, v):
|
31
|
+
if not v or not v.strip():
|
32
|
+
raise ValueError("Merchant ID is required")
|
33
|
+
return v.strip()
|
34
|
+
|
35
|
+
@validator('api_key')
|
36
|
+
def validate_api_key(cls, v):
|
37
|
+
if not v or len(v) < 10:
|
38
|
+
raise ValueError("API key must be at least 10 characters")
|
39
|
+
return v
|
40
|
+
|
41
|
+
|
42
|
+
class CryptomusProvider(PaymentProvider):
|
43
|
+
"""Cryptomus payment provider with universal field mapping."""
|
44
|
+
|
45
|
+
def __init__(self, config: CryptomusConfig):
|
46
|
+
super().__init__(config)
|
47
|
+
self.merchant_id = config.merchant_id
|
48
|
+
self.api_key = config.api_key
|
49
|
+
self.test_mode = config.test_mode
|
50
|
+
self.base_url = "https://api.cryptomus.com/v1" if not config.test_mode else "https://api.cryptomus.com/v1"
|
51
|
+
|
52
|
+
def create_payment(self, payment_data: dict) -> ProviderResponse:
|
53
|
+
"""
|
54
|
+
Create payment using Cryptomus API.
|
55
|
+
Maps to universal payment fields.
|
56
|
+
"""
|
57
|
+
try:
|
58
|
+
# Extract required data
|
59
|
+
order_id = payment_data.get('order_id')
|
60
|
+
amount = payment_data.get('amount')
|
61
|
+
currency = payment_data.get('currency', 'USD')
|
62
|
+
callback_url = payment_data.get('callback_url', self.config.callback_url)
|
63
|
+
|
64
|
+
if not all([order_id, amount]):
|
65
|
+
return ProviderResponse(
|
66
|
+
success=False,
|
67
|
+
error_message="Missing required fields: order_id, amount"
|
68
|
+
)
|
69
|
+
|
70
|
+
# Prepare Cryptomus API request
|
71
|
+
payload = {
|
72
|
+
"amount": str(amount),
|
73
|
+
"currency": currency,
|
74
|
+
"order_id": order_id,
|
75
|
+
"url_callback": callback_url,
|
76
|
+
"url_return": payment_data.get('return_url'),
|
77
|
+
"url_success": payment_data.get('success_url'),
|
78
|
+
"is_payment_multiple": False,
|
79
|
+
"lifetime": 3600, # 1 hour
|
80
|
+
"to_currency": payment_data.get('crypto_currency', 'BTC')
|
81
|
+
}
|
82
|
+
|
83
|
+
# Generate signature
|
84
|
+
headers = self._generate_headers(payload)
|
85
|
+
|
86
|
+
# Make API request
|
87
|
+
response = requests.post(
|
88
|
+
f"{self.base_url}/payment",
|
89
|
+
json=payload,
|
90
|
+
headers=headers,
|
91
|
+
timeout=30
|
92
|
+
)
|
93
|
+
|
94
|
+
if response.status_code == 200:
|
95
|
+
result = response.json()
|
96
|
+
|
97
|
+
if result.get('state') == 0: # Success
|
98
|
+
payment_info = result.get('result', {})
|
99
|
+
|
100
|
+
return ProviderResponse(
|
101
|
+
success=True,
|
102
|
+
transaction_id=payment_info.get('uuid'),
|
103
|
+
data={
|
104
|
+
# Universal field mapping
|
105
|
+
'provider_payment_id': payment_info.get('uuid'),
|
106
|
+
'receiver_address': payment_info.get('address'),
|
107
|
+
'crypto_amount': float(payment_info.get('amount', 0)),
|
108
|
+
'provider_callback_url': callback_url,
|
109
|
+
'payment_url': payment_info.get('url'),
|
110
|
+
'qr_code': payment_info.get('static_qr'),
|
111
|
+
|
112
|
+
# Cryptomus specific fields
|
113
|
+
'cryptomus_order_id': payment_info.get('order_id'),
|
114
|
+
'cryptomus_currency': payment_info.get('currency'),
|
115
|
+
'cryptomus_network': payment_info.get('network'),
|
116
|
+
'cryptomus_status': payment_info.get('status'),
|
117
|
+
'expires_at': payment_info.get('expired_at')
|
118
|
+
}
|
119
|
+
)
|
120
|
+
else:
|
121
|
+
error_msg = result.get('message', 'Unknown Cryptomus error')
|
122
|
+
return ProviderResponse(
|
123
|
+
success=False,
|
124
|
+
error_message=f"Cryptomus API error: {error_msg}"
|
125
|
+
)
|
126
|
+
else:
|
127
|
+
return ProviderResponse(
|
128
|
+
success=False,
|
129
|
+
error_message=f"HTTP {response.status_code}: {response.text}"
|
130
|
+
)
|
131
|
+
|
132
|
+
except requests.RequestException as e:
|
133
|
+
logger.error(f"Cryptomus API request failed: {e}")
|
134
|
+
return ProviderResponse(
|
135
|
+
success=False,
|
136
|
+
error_message=f"Network error: {str(e)}"
|
137
|
+
)
|
138
|
+
except Exception as e:
|
139
|
+
logger.error(f"Cryptomus payment creation failed: {e}")
|
140
|
+
return ProviderResponse(
|
141
|
+
success=False,
|
142
|
+
error_message=f"Unexpected error: {str(e)}"
|
143
|
+
)
|
144
|
+
|
145
|
+
def validate_webhook(self, webhook_data: Dict[str, Any],
|
146
|
+
request_headers: Dict[str, str], raw_body: bytes) -> tuple[bool, Optional[str]]:
|
147
|
+
"""
|
148
|
+
Validate Cryptomus webhook signature and required fields.
|
149
|
+
"""
|
150
|
+
try:
|
151
|
+
# Check required fields
|
152
|
+
required_fields = ['uuid', 'order_id', 'amount', 'currency', 'status']
|
153
|
+
for field in required_fields:
|
154
|
+
if field not in webhook_data:
|
155
|
+
return False, f"Missing required field: {field}"
|
156
|
+
|
157
|
+
# Validate signature if provided
|
158
|
+
sign = request_headers.get('sign') or webhook_data.get('sign')
|
159
|
+
if sign:
|
160
|
+
# Generate expected signature
|
161
|
+
expected_sign = self._generate_webhook_signature(webhook_data)
|
162
|
+
if sign != expected_sign:
|
163
|
+
return False, "Invalid webhook signature"
|
164
|
+
|
165
|
+
return True, None
|
166
|
+
|
167
|
+
except Exception as e:
|
168
|
+
logger.error(f"Cryptomus webhook validation failed: {e}")
|
169
|
+
return False, f"Validation error: {str(e)}"
|
170
|
+
|
171
|
+
def process_webhook(self, webhook_data: Dict[str, Any]) -> ProviderResponse:
|
172
|
+
"""
|
173
|
+
Process Cryptomus webhook and map to universal fields.
|
174
|
+
"""
|
175
|
+
try:
|
176
|
+
# Map Cryptomus webhook fields to universal fields
|
177
|
+
universal_data = {
|
178
|
+
'provider_payment_id': webhook_data.get('uuid'),
|
179
|
+
'status': self._map_status(webhook_data.get('status')),
|
180
|
+
'transaction_hash': webhook_data.get('txid'),
|
181
|
+
'sender_address': webhook_data.get('from'),
|
182
|
+
'receiver_address': webhook_data.get('to'),
|
183
|
+
'crypto_amount': float(webhook_data.get('amount', 0)),
|
184
|
+
'confirmations_count': int(webhook_data.get('confirmations', 0)),
|
185
|
+
|
186
|
+
# Additional Cryptomus data
|
187
|
+
'cryptomus_network': webhook_data.get('network'),
|
188
|
+
'cryptomus_currency': webhook_data.get('currency'),
|
189
|
+
'cryptomus_commission': webhook_data.get('commission'),
|
190
|
+
'updated_at': webhook_data.get('updated_at')
|
191
|
+
}
|
192
|
+
|
193
|
+
return ProviderResponse(
|
194
|
+
success=True,
|
195
|
+
data=universal_data
|
196
|
+
)
|
197
|
+
|
198
|
+
except Exception as e:
|
199
|
+
logger.error(f"Cryptomus webhook processing failed: {e}")
|
200
|
+
return ProviderResponse(
|
201
|
+
success=False,
|
202
|
+
error_message=f"Webhook processing error: {str(e)}"
|
203
|
+
)
|
204
|
+
|
205
|
+
def get_payment_status(self, payment_id: str) -> ProviderResponse:
|
206
|
+
"""Get payment status from Cryptomus."""
|
207
|
+
try:
|
208
|
+
payload = {"uuid": payment_id}
|
209
|
+
headers = self._generate_headers(payload)
|
210
|
+
|
211
|
+
response = requests.post(
|
212
|
+
f"{self.base_url}/payment/info",
|
213
|
+
json=payload,
|
214
|
+
headers=headers,
|
215
|
+
timeout=30
|
216
|
+
)
|
217
|
+
|
218
|
+
if response.status_code == 200:
|
219
|
+
result = response.json()
|
220
|
+
if result.get('state') == 0:
|
221
|
+
payment_info = result.get('result', {})
|
222
|
+
|
223
|
+
return ProviderResponse(
|
224
|
+
success=True,
|
225
|
+
data={
|
226
|
+
'status': self._map_status(payment_info.get('status')),
|
227
|
+
'provider_payment_id': payment_info.get('uuid'),
|
228
|
+
'transaction_hash': payment_info.get('txid'),
|
229
|
+
'crypto_amount': float(payment_info.get('amount', 0)),
|
230
|
+
'confirmations_count': int(payment_info.get('confirmations', 0))
|
231
|
+
}
|
232
|
+
)
|
233
|
+
|
234
|
+
return ProviderResponse(
|
235
|
+
success=False,
|
236
|
+
error_message="Failed to get payment status"
|
237
|
+
)
|
238
|
+
|
239
|
+
except Exception as e:
|
240
|
+
logger.error(f"Cryptomus status check failed: {e}")
|
241
|
+
return ProviderResponse(
|
242
|
+
success=False,
|
243
|
+
error_message=f"Status check error: {str(e)}"
|
244
|
+
)
|
245
|
+
|
246
|
+
def _generate_headers(self, payload: dict) -> dict:
|
247
|
+
"""Generate required headers for Cryptomus API."""
|
248
|
+
data_string = base64.b64encode(json.dumps(payload).encode()).decode()
|
249
|
+
sign = hashlib.md5(f"{data_string}{self.api_key}".encode()).hexdigest()
|
250
|
+
|
251
|
+
return {
|
252
|
+
"merchant": self.merchant_id,
|
253
|
+
"sign": sign,
|
254
|
+
"Content-Type": "application/json"
|
255
|
+
}
|
256
|
+
|
257
|
+
def _generate_webhook_signature(self, webhook_data: dict) -> str:
|
258
|
+
"""Generate expected webhook signature for validation."""
|
259
|
+
# Cryptomus webhook signature generation
|
260
|
+
data_string = base64.b64encode(json.dumps(webhook_data, sort_keys=True).encode()).decode()
|
261
|
+
return hashlib.md5(f"{data_string}{self.api_key}".encode()).hexdigest()
|
262
|
+
|
263
|
+
def _map_status(self, cryptomus_status: str) -> str:
|
264
|
+
"""Map Cryptomus status to universal status."""
|
265
|
+
status_mapping = {
|
266
|
+
'check': 'pending',
|
267
|
+
'process': 'pending',
|
268
|
+
'confirm_check': 'pending',
|
269
|
+
'confirmed': 'completed',
|
270
|
+
'fail': 'failed',
|
271
|
+
'cancel': 'cancelled',
|
272
|
+
'system_fail': 'failed',
|
273
|
+
'refund_process': 'refunding',
|
274
|
+
'refund_fail': 'failed',
|
275
|
+
'refund_paid': 'refunded'
|
276
|
+
}
|
277
|
+
return status_mapping.get(cryptomus_status, 'pending')
|
278
|
+
|
279
|
+
def get_supported_currencies(self) -> ProviderResponse:
|
280
|
+
"""Get supported currencies from Cryptomus."""
|
281
|
+
try:
|
282
|
+
headers = self._generate_headers({})
|
283
|
+
|
284
|
+
response = requests.post(
|
285
|
+
f"{self.base_url}/exchange-rate/list",
|
286
|
+
json={},
|
287
|
+
headers=headers,
|
288
|
+
timeout=30
|
289
|
+
)
|
290
|
+
|
291
|
+
if response.status_code == 200:
|
292
|
+
result = response.json()
|
293
|
+
if result.get('state') == 0:
|
294
|
+
currencies = result.get('result', [])
|
295
|
+
return ProviderResponse(
|
296
|
+
success=True,
|
297
|
+
data={'currencies': currencies}
|
298
|
+
)
|
299
|
+
|
300
|
+
return ProviderResponse(
|
301
|
+
success=False,
|
302
|
+
error_message="Failed to get supported currencies"
|
303
|
+
)
|
304
|
+
|
305
|
+
except Exception as e:
|
306
|
+
logger.error(f"Cryptomus currencies request failed: {e}")
|
307
|
+
return ProviderResponse(
|
308
|
+
success=False,
|
309
|
+
error_message=f"Currencies request error: {str(e)}"
|
310
|
+
)
|
@@ -10,6 +10,7 @@ from typing import Optional, List
|
|
10
10
|
from .base import PaymentProvider
|
11
11
|
from .nowpayments import NowPaymentsProvider, NowPaymentsConfig
|
12
12
|
from .cryptapi import CryptAPIProvider, CryptAPIConfig
|
13
|
+
from .cryptomus import CryptomusProvider, CryptomusConfig
|
13
14
|
|
14
15
|
logger = logging.getLogger(__name__)
|
15
16
|
|
@@ -47,6 +48,9 @@ class ProviderRegistry:
|
|
47
48
|
elif name == 'cryptapi':
|
48
49
|
config = CryptAPIConfig(**config_dict)
|
49
50
|
return CryptAPIProvider(config)
|
51
|
+
elif name == 'cryptomus':
|
52
|
+
config = CryptomusConfig(**config_dict)
|
53
|
+
return CryptomusProvider(config)
|
50
54
|
elif name == 'stripe':
|
51
55
|
# TODO: Implement StripeProvider with StripeConfig
|
52
56
|
return None
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""
|
2
|
+
Security services for payment system.
|
3
|
+
Foundation layer security components.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from .webhook_validator import webhook_validator, WebhookValidator
|
7
|
+
from .error_handler import (
|
8
|
+
error_handler,
|
9
|
+
CentralizedErrorHandler,
|
10
|
+
PaymentError,
|
11
|
+
SecurityError,
|
12
|
+
ProviderError,
|
13
|
+
ValidationError,
|
14
|
+
ErrorSeverity,
|
15
|
+
ErrorCategory,
|
16
|
+
error_context
|
17
|
+
)
|
18
|
+
from .payment_notifications import payment_notifications, PaymentNotifications
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
'webhook_validator',
|
22
|
+
'WebhookValidator',
|
23
|
+
'error_handler',
|
24
|
+
'CentralizedErrorHandler',
|
25
|
+
'PaymentError',
|
26
|
+
'SecurityError',
|
27
|
+
'ProviderError',
|
28
|
+
'ValidationError',
|
29
|
+
'ErrorSeverity',
|
30
|
+
'ErrorCategory',
|
31
|
+
'error_context',
|
32
|
+
'payment_notifications',
|
33
|
+
'PaymentNotifications'
|
34
|
+
]
|