django-cfg 1.2.23__py3-none-any.whl → 1.2.27__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.
Files changed (85) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
  3. django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
  4. django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
  5. django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
  6. django_cfg/apps/payments/config/__init__.py +15 -37
  7. django_cfg/apps/payments/config/module.py +30 -122
  8. django_cfg/apps/payments/config/providers.py +28 -16
  9. django_cfg/apps/payments/config/settings.py +53 -93
  10. django_cfg/apps/payments/config/utils.py +10 -156
  11. django_cfg/apps/payments/management/__init__.py +3 -0
  12. django_cfg/apps/payments/management/commands/README.md +178 -0
  13. django_cfg/apps/payments/management/commands/__init__.py +3 -0
  14. django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
  15. django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
  16. django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
  17. django_cfg/apps/payments/managers/currency_manager.py +65 -14
  18. django_cfg/apps/payments/middleware/api_access.py +33 -0
  19. django_cfg/apps/payments/migrations/0001_initial.py +94 -1
  20. django_cfg/apps/payments/models/payments.py +110 -0
  21. django_cfg/apps/payments/services/__init__.py +7 -1
  22. django_cfg/apps/payments/services/core/balance_service.py +14 -16
  23. django_cfg/apps/payments/services/core/fallback_service.py +432 -0
  24. django_cfg/apps/payments/services/core/payment_service.py +212 -29
  25. django_cfg/apps/payments/services/core/subscription_service.py +15 -17
  26. django_cfg/apps/payments/services/internal_types.py +31 -0
  27. django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
  28. django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
  29. django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
  30. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  31. django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
  32. django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
  33. django_cfg/apps/payments/services/providers/registry.py +4 -0
  34. django_cfg/apps/payments/services/security/__init__.py +34 -0
  35. django_cfg/apps/payments/services/security/error_handler.py +637 -0
  36. django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
  37. django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
  38. django_cfg/apps/payments/signals/api_key_signals.py +10 -0
  39. django_cfg/apps/payments/signals/payment_signals.py +3 -2
  40. django_cfg/apps/payments/tasks/__init__.py +12 -0
  41. django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
  42. django_cfg/apps/payments/utils/__init__.py +7 -4
  43. django_cfg/apps/payments/utils/billing_utils.py +342 -0
  44. django_cfg/apps/payments/utils/config_utils.py +2 -0
  45. django_cfg/apps/payments/views/payment_views.py +40 -2
  46. django_cfg/apps/payments/views/webhook_views.py +266 -0
  47. django_cfg/apps/payments/viewsets.py +65 -0
  48. django_cfg/cli/README.md +2 -2
  49. django_cfg/cli/commands/create_project.py +1 -1
  50. django_cfg/cli/commands/info.py +1 -1
  51. django_cfg/cli/main.py +1 -1
  52. django_cfg/cli/utils.py +5 -5
  53. django_cfg/core/config.py +18 -4
  54. django_cfg/models/payments.py +547 -0
  55. django_cfg/models/tasks.py +51 -2
  56. django_cfg/modules/base.py +11 -5
  57. django_cfg/modules/django_currency/README.md +104 -269
  58. django_cfg/modules/django_currency/__init__.py +99 -41
  59. django_cfg/modules/django_currency/clients/__init__.py +11 -0
  60. django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
  61. django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
  62. django_cfg/modules/django_currency/core/__init__.py +42 -0
  63. django_cfg/modules/django_currency/core/converter.py +169 -0
  64. django_cfg/modules/django_currency/core/exceptions.py +28 -0
  65. django_cfg/modules/django_currency/core/models.py +54 -0
  66. django_cfg/modules/django_currency/database/__init__.py +25 -0
  67. django_cfg/modules/django_currency/database/database_loader.py +507 -0
  68. django_cfg/modules/django_currency/utils/__init__.py +9 -0
  69. django_cfg/modules/django_currency/utils/cache.py +92 -0
  70. django_cfg/registry/core.py +10 -0
  71. django_cfg/template_archive/__init__.py +0 -0
  72. django_cfg/template_archive/django_sample.zip +0 -0
  73. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/METADATA +10 -6
  74. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/RECORD +77 -51
  75. django_cfg/apps/agents/examples/__init__.py +0 -3
  76. django_cfg/apps/agents/examples/simple_example.py +0 -161
  77. django_cfg/apps/knowbase/examples/__init__.py +0 -3
  78. django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
  79. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
  80. django_cfg/modules/django_currency/cache.py +0 -430
  81. django_cfg/modules/django_currency/converter.py +0 -324
  82. django_cfg/modules/django_currency/service.py +0 -277
  83. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/WHEEL +0 -0
  84. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/entry_points.txt +0 -0
  85. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.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
+ ]