django-cfg 1.2.29__py3-none-any.whl → 1.2.31__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 (126) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/__init__.py +3 -2
  3. django_cfg/apps/payments/admin/balance_admin.py +18 -18
  4. django_cfg/apps/payments/admin/currencies_admin.py +319 -131
  5. django_cfg/apps/payments/admin/payments_admin.py +15 -4
  6. django_cfg/apps/payments/config/module.py +2 -2
  7. django_cfg/apps/payments/config/utils.py +2 -2
  8. django_cfg/apps/payments/decorators.py +2 -2
  9. django_cfg/apps/payments/management/commands/README.md +95 -127
  10. django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
  11. django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
  12. django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
  13. django_cfg/apps/payments/managers/__init__.py +3 -2
  14. django_cfg/apps/payments/managers/balance_manager.py +2 -2
  15. django_cfg/apps/payments/managers/currency_manager.py +272 -49
  16. django_cfg/apps/payments/managers/payment_manager.py +161 -13
  17. django_cfg/apps/payments/middleware/api_access.py +2 -2
  18. django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
  19. django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
  20. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
  21. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
  22. django_cfg/apps/payments/models/__init__.py +3 -2
  23. django_cfg/apps/payments/models/currencies.py +187 -71
  24. django_cfg/apps/payments/models/payments.py +3 -2
  25. django_cfg/apps/payments/serializers/__init__.py +3 -2
  26. django_cfg/apps/payments/serializers/currencies.py +20 -12
  27. django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
  28. django_cfg/apps/payments/services/core/balance_service.py +2 -2
  29. django_cfg/apps/payments/services/core/fallback_service.py +2 -2
  30. django_cfg/apps/payments/services/core/payment_service.py +3 -6
  31. django_cfg/apps/payments/services/core/subscription_service.py +4 -7
  32. django_cfg/apps/payments/services/internal_types.py +171 -7
  33. django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
  34. django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
  35. django_cfg/apps/payments/services/providers/base.py +144 -43
  36. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
  37. django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
  38. django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
  39. django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
  40. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
  41. django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
  42. django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
  43. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
  44. django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
  45. django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
  46. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
  47. django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
  48. django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
  49. django_cfg/apps/payments/services/providers/registry.py +294 -11
  50. django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
  51. django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
  52. django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
  53. django_cfg/apps/payments/services/security/error_handler.py +6 -8
  54. django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
  55. django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
  56. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  57. django_cfg/apps/payments/signals/payment_signals.py +11 -5
  58. django_cfg/apps/payments/signals/subscription_signals.py +2 -2
  59. django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
  60. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
  61. django_cfg/apps/payments/templates/payments/base.html +4 -4
  62. django_cfg/apps/payments/templates/payments/components/payment_card.html +6 -6
  63. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +4 -4
  64. django_cfg/apps/payments/templates/payments/components/progress_bar.html +14 -7
  65. django_cfg/apps/payments/templates/payments/components/provider_stats.html +2 -2
  66. django_cfg/apps/payments/templates/payments/components/status_badge.html +8 -1
  67. django_cfg/apps/payments/templates/payments/components/status_overview.html +34 -30
  68. django_cfg/apps/payments/templates/payments/dashboard.html +202 -290
  69. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
  70. django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
  71. django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
  72. django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
  73. django_cfg/apps/payments/templates/payments/stats.html +261 -0
  74. django_cfg/apps/payments/templates/payments/test.html +213 -0
  75. django_cfg/apps/payments/urls.py +3 -1
  76. django_cfg/apps/payments/{urls_templates.py → urls_admin.py} +6 -0
  77. django_cfg/apps/payments/utils/__init__.py +1 -3
  78. django_cfg/apps/payments/utils/billing_utils.py +2 -2
  79. django_cfg/apps/payments/utils/config_utils.py +2 -8
  80. django_cfg/apps/payments/utils/validation_utils.py +2 -2
  81. django_cfg/apps/payments/views/__init__.py +3 -2
  82. django_cfg/apps/payments/views/currency_views.py +31 -20
  83. django_cfg/apps/payments/views/payment_views.py +2 -2
  84. django_cfg/apps/payments/views/templates/ajax.py +141 -2
  85. django_cfg/apps/payments/views/templates/base.py +21 -13
  86. django_cfg/apps/payments/views/templates/payment_detail.py +1 -1
  87. django_cfg/apps/payments/views/templates/payment_management.py +34 -40
  88. django_cfg/apps/payments/views/templates/stats.py +8 -4
  89. django_cfg/apps/payments/views/webhook_views.py +2 -2
  90. django_cfg/apps/payments/viewsets.py +3 -2
  91. django_cfg/apps/tasks/urls.py +0 -2
  92. django_cfg/apps/tasks/urls_admin.py +14 -0
  93. django_cfg/apps/urls.py +4 -4
  94. django_cfg/core/config.py +35 -0
  95. django_cfg/models/payments.py +2 -8
  96. django_cfg/modules/django_currency/__init__.py +16 -11
  97. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  98. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  99. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  100. django_cfg/modules/django_currency/core/__init__.py +1 -7
  101. django_cfg/modules/django_currency/core/converter.py +18 -23
  102. django_cfg/modules/django_currency/core/models.py +122 -11
  103. django_cfg/modules/django_currency/database/__init__.py +4 -4
  104. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  105. django_cfg/modules/django_unfold/dashboard.py +7 -2
  106. django_cfg/template_archive/django_sample.zip +0 -0
  107. django_cfg/templates/admin/components/action_grid.html +9 -9
  108. django_cfg/templates/admin/components/metric_card.html +5 -5
  109. django_cfg/templates/admin/components/status_badge.html +2 -2
  110. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  111. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  112. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  113. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  114. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/METADATA +2 -4
  115. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/RECORD +118 -96
  116. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  117. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  118. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  119. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  120. django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
  121. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  122. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  123. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  124. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
  125. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
  126. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,429 @@
1
+ """
2
+ Cryptomus payment provider implementation.
3
+ """
4
+
5
+ from django_cfg.modules.django_logger import get_logger
6
+ import hashlib
7
+ import json
8
+ import base64
9
+ from datetime import datetime
10
+ from decimal import Decimal
11
+ from typing import Dict, Any, Optional, List
12
+ from dataclasses import dataclass
13
+
14
+ import requests
15
+ from ..base import PaymentProvider
16
+ from ...internal_types import ProviderResponse, PaymentAmountEstimate, WebhookData
17
+ from .models import CryptomusConfig
18
+
19
+ logger = get_logger("cryptomus_old")
20
+
21
+
22
+ class CryptomusProvider(PaymentProvider):
23
+ """Cryptomus payment provider with universal field mapping."""
24
+
25
+ def __init__(self, config: CryptomusConfig):
26
+ super().__init__(config)
27
+ self.config = config
28
+ self.merchant_id = config.merchant_id
29
+ self.api_key = config.api_key
30
+ self.test_mode = config.test_mode
31
+ self.base_url = self._get_base_url()
32
+
33
+ def _get_base_url(self) -> str:
34
+ """Get base URL for API requests."""
35
+ if self.config.test_mode:
36
+ return "https://api.cryptomus.com/v1/test" # Test mode URL for identification
37
+ return "https://api.cryptomus.com/v1"
38
+
39
+ def _make_request(self, method: str, endpoint: str, data: Optional[dict] = None) -> Optional[dict]:
40
+ """Make HTTP request to Cryptomus API with error handling."""
41
+ try:
42
+ url = f"{self.base_url}/{endpoint}" if endpoint else self.base_url
43
+
44
+ # Generate headers with signature for POST requests
45
+ headers = self._generate_headers(data or {})
46
+
47
+ response = requests.request(
48
+ method=method,
49
+ url=url,
50
+ headers=headers,
51
+ json=data,
52
+ timeout=30
53
+ )
54
+
55
+ response.raise_for_status()
56
+ return response.json()
57
+
58
+ except requests.exceptions.RequestException as e:
59
+ logger.error(f"Cryptomus API request failed: {e}")
60
+ return None
61
+ except Exception as e:
62
+ logger.error(f"Unexpected error in Cryptomus request: {e}")
63
+ return None
64
+
65
+ def check_payment_status(self, payment_id: str) -> ProviderResponse:
66
+ """Check payment status via Cryptomus API."""
67
+ try:
68
+ endpoint = f"payment/{payment_id}"
69
+ response = self._make_request('POST', endpoint, {})
70
+
71
+ if response and 'state' in response and response['state'] == 0:
72
+ result = response.get('result', {})
73
+ return ProviderResponse(
74
+ success=True,
75
+ provider_payment_id=result.get('uuid', payment_id),
76
+ status=result.get('status', 'pending'),
77
+ pay_address=result.get('address'),
78
+ amount=Decimal(str(result.get('amount', 0))),
79
+ currency=result.get('currency', 'unknown'),
80
+ data=result
81
+ )
82
+ else:
83
+ return ProviderResponse(
84
+ success=False,
85
+ error_message=response.get('message', 'Failed to check payment status')
86
+ )
87
+
88
+ except Exception as e:
89
+ logger.error(f"Cryptomus check_payment_status error: {e}")
90
+ return ProviderResponse(
91
+ success=False,
92
+ error_message=str(e)
93
+ )
94
+
95
+ def create_payment(self, payment_data: dict) -> ProviderResponse:
96
+ """
97
+ Create payment using Cryptomus API.
98
+ Maps to universal payment fields.
99
+ """
100
+ try:
101
+ # Extract required data
102
+ order_id = payment_data.get('order_id')
103
+ amount = payment_data.get('amount')
104
+ currency = payment_data.get('currency', 'USD')
105
+ callback_url = payment_data.get('callback_url', self.config.callback_url)
106
+
107
+ if not all([order_id, amount]):
108
+ return ProviderResponse(
109
+ success=False,
110
+ error_message="Missing required fields: order_id, amount"
111
+ )
112
+
113
+ # Prepare Cryptomus API request
114
+ payload = {
115
+ "amount": str(amount),
116
+ "currency": currency,
117
+ "order_id": order_id,
118
+ "url_callback": callback_url,
119
+ "url_return": payment_data.get('return_url'),
120
+ "url_success": payment_data.get('success_url', self.config.success_url),
121
+ "url_cancel": payment_data.get('cancel_url', self.config.cancel_url),
122
+ "is_payment_multiple": False,
123
+ "lifetime": 3600, # 1 hour
124
+ "to_currency": payment_data.get('crypto_currency', 'BTC')
125
+ }
126
+
127
+ # Make API request using centralized method
128
+ result = self._make_request('POST', 'payment', payload)
129
+
130
+ if result and result.get('state') == 0: # Success
131
+ payment_info = result.get('result', {})
132
+
133
+ return ProviderResponse(
134
+ success=True,
135
+ provider_payment_id=payment_info.get('uuid'),
136
+ data={
137
+ # Universal field mapping
138
+ 'provider_payment_id': payment_info.get('uuid'),
139
+ 'receiver_address': payment_info.get('address'),
140
+ 'crypto_amount': float(payment_info.get('amount', 0)),
141
+ 'provider_callback_url': callback_url,
142
+ 'payment_url': payment_info.get('url'),
143
+ 'qr_code': payment_info.get('static_qr'),
144
+
145
+ # Cryptomus specific fields
146
+ 'cryptomus_order_id': payment_info.get('order_id'),
147
+ 'cryptomus_currency': payment_info.get('currency'),
148
+ 'cryptomus_network': payment_info.get('network'),
149
+ 'cryptomus_status': payment_info.get('status'),
150
+ 'expires_at': payment_info.get('expired_at')
151
+ }
152
+ )
153
+ else:
154
+ error_msg = result.get('message', 'Unknown Cryptomus error') if result else 'No response from API'
155
+ return ProviderResponse(
156
+ success=False,
157
+ error_message=f"Cryptomus API error: {error_msg}"
158
+ )
159
+
160
+ except requests.RequestException as e:
161
+ logger.error(f"Cryptomus API request failed: {e}")
162
+ return ProviderResponse(
163
+ success=False,
164
+ error_message=f"Network error: {str(e)}"
165
+ )
166
+ except Exception as e:
167
+ logger.error(f"Cryptomus payment creation failed: {e}")
168
+ return ProviderResponse(
169
+ success=False,
170
+ error_message=f"Unexpected error: {str(e)}"
171
+ )
172
+
173
+ def validate_webhook(self, webhook_data: Dict[str, Any],
174
+ request_headers: Optional[Dict[str, str]] = None,
175
+ raw_body: Optional[bytes] = None) -> bool:
176
+ """
177
+ Validate Cryptomus webhook with strict requirements.
178
+ """
179
+ try:
180
+ # Strict required field validation
181
+ required_fields = ['uuid', 'order_id', 'amount', 'currency', 'status']
182
+ for field in required_fields:
183
+ if field not in webhook_data or not webhook_data[field]:
184
+ logger.warning(f"Missing or empty required field: {field}")
185
+ return False
186
+
187
+ # Validate signature (required for security)
188
+ sign = None
189
+ if request_headers:
190
+ sign = request_headers.get('sign')
191
+ if not sign:
192
+ sign = webhook_data.get('sign')
193
+
194
+ if not sign:
195
+ logger.error("No signature found in Cryptomus webhook")
196
+ return False # Require signature for security
197
+
198
+ # Verify signature
199
+ data_for_sign = {k: v for k, v in webhook_data.items() if k != 'sign'}
200
+ expected_sign = self._generate_webhook_signature(data_for_sign)
201
+ if sign != expected_sign:
202
+ logger.error("Cryptomus webhook signature validation failed")
203
+ return False
204
+
205
+ return True
206
+
207
+ except Exception as e:
208
+ logger.error(f"Cryptomus webhook validation failed: {e}")
209
+ return False
210
+
211
+ def process_webhook(self, webhook_data: Dict[str, Any]) -> WebhookData:
212
+ """
213
+ Process Cryptomus webhook and map to universal fields.
214
+ """
215
+ try:
216
+ # Map Cryptomus status to universal status
217
+ status = self._map_status(webhook_data.get('status', 'unknown'))
218
+
219
+ # Extract payment amount
220
+ amount = webhook_data.get('amount')
221
+ pay_amount = Decimal(str(amount)) if amount else Decimal('0')
222
+
223
+ # Ensure provider_payment_id is not None
224
+ provider_payment_id = webhook_data.get('uuid') or webhook_data.get('order_id') or 'unknown'
225
+
226
+ return WebhookData(
227
+ provider_payment_id=provider_payment_id,
228
+ status=status,
229
+ pay_amount=pay_amount,
230
+ pay_currency=webhook_data.get('currency', 'unknown'),
231
+ actually_paid=pay_amount, # For Cryptomus, same as pay_amount
232
+ order_id=webhook_data.get('order_id'),
233
+ signature=webhook_data.get('txid') or webhook_data.get('sign') # Use transaction ID or signature
234
+ )
235
+
236
+ except Exception as e:
237
+ logger.error(f"Cryptomus webhook processing failed: {e}")
238
+ raise
239
+
240
+ def estimate_payment_amount(self, amount: Decimal, currency_code: str) -> Optional['PaymentAmountEstimate']:
241
+ """Estimate payment amount using Cryptomus exchange rates."""
242
+ try:
243
+ # Cryptomus exchange rate endpoint
244
+ response = self._make_request('POST', 'exchange-rate/list', {
245
+ 'currency_from': 'USD',
246
+ 'currency_to': currency_code.upper(),
247
+ 'amount': float(amount)
248
+ })
249
+
250
+ if response and response.get('state') == 0:
251
+ result = response.get('result', {})
252
+ if result:
253
+ return PaymentAmountEstimate(
254
+ currency_from='usd',
255
+ currency_to=currency_code.lower(),
256
+ amount_from=amount,
257
+ estimated_amount=Decimal(str(result.get('amount', 0))),
258
+ exchange_rate=Decimal(str(result.get('course', 1))),
259
+ provider_name=self.name,
260
+ estimated_at=datetime.now()
261
+ )
262
+
263
+ return None
264
+
265
+ except Exception as e:
266
+ logger.error(f"Cryptomus estimate_payment_amount error: {e}")
267
+ return None
268
+
269
+ def get_payment_status(self, payment_id: str) -> ProviderResponse:
270
+ """Get payment status from Cryptomus."""
271
+ try:
272
+ payload = {"uuid": payment_id}
273
+ headers = self._generate_headers(payload)
274
+
275
+ response = requests.post(
276
+ f"{self.base_url}/payment/info",
277
+ json=payload,
278
+ headers=headers,
279
+ timeout=30
280
+ )
281
+
282
+ if response.status_code == 200:
283
+ result = response.json()
284
+ if result.get('state') == 0:
285
+ payment_info = result.get('result', {})
286
+
287
+ return ProviderResponse(
288
+ success=True,
289
+ data={
290
+ 'status': self._map_status(payment_info.get('status')),
291
+ 'provider_payment_id': payment_info.get('uuid'),
292
+ 'transaction_hash': payment_info.get('txid'),
293
+ 'crypto_amount': float(payment_info.get('amount', 0)),
294
+ 'confirmations_count': int(payment_info.get('confirmations', 0))
295
+ }
296
+ )
297
+
298
+ return ProviderResponse(
299
+ success=False,
300
+ error_message="Failed to get payment status"
301
+ )
302
+
303
+ except Exception as e:
304
+ logger.error(f"Cryptomus status check failed: {e}")
305
+ return ProviderResponse(
306
+ success=False,
307
+ error_message=f"Status check error: {str(e)}"
308
+ )
309
+
310
+ def _generate_headers(self, payload: dict) -> dict:
311
+ """Generate authentication headers for Cryptomus API."""
312
+ # Convert data to JSON string with sorted keys for consistency
313
+ data_json = json.dumps(payload, sort_keys=True, separators=(',', ':'))
314
+
315
+ # Create signature: md5(base64(data) + api_key)
316
+ data_b64 = base64.b64encode(data_json.encode('utf-8')).decode('utf-8')
317
+ signature_string = data_b64 + self.config.api_key
318
+ signature = hashlib.md5(signature_string.encode('utf-8')).hexdigest()
319
+
320
+ return {
321
+ 'Content-Type': 'application/json',
322
+ 'merchant': self.config.merchant_id, # CRITICAL: merchant header
323
+ 'sign': signature
324
+ }
325
+
326
+ def _generate_webhook_signature(self, webhook_data: dict) -> str:
327
+ """Generate expected webhook signature for validation."""
328
+ # Cryptomus webhook signature generation
329
+ data_string = base64.b64encode(json.dumps(webhook_data, sort_keys=True).encode()).decode()
330
+ return hashlib.md5(f"{data_string}{self.api_key}".encode()).hexdigest()
331
+
332
+ def _map_status(self, cryptomus_status: str) -> str:
333
+ """Map Cryptomus status to universal status."""
334
+ status_mapping = {
335
+ 'check': 'pending',
336
+ 'process': 'pending',
337
+ 'confirm_check': 'pending',
338
+ 'paid': 'completed', # Add missing 'paid' status
339
+ 'confirmed': 'completed',
340
+ 'fail': 'failed',
341
+ 'cancel': 'cancelled',
342
+ 'system_fail': 'failed',
343
+ 'refund_process': 'refunding',
344
+ 'refund_fail': 'failed',
345
+ 'refund_paid': 'refunded'
346
+ }
347
+ return status_mapping.get(cryptomus_status, 'pending')
348
+
349
+ def get_supported_currencies(self) -> ProviderResponse:
350
+ """Get supported currencies from Cryptomus."""
351
+ try:
352
+ result = self._make_request('POST', 'exchange-rate/list', {})
353
+
354
+ if result and result.get('state') == 0:
355
+ currencies = result.get('result', [])
356
+ return ProviderResponse(
357
+ success=True,
358
+ data={'currencies': currencies}
359
+ )
360
+
361
+ return ProviderResponse(
362
+ success=False,
363
+ error_message="Failed to get supported currencies"
364
+ )
365
+
366
+ except Exception as e:
367
+ logger.error(f"Cryptomus currencies request failed: {e}")
368
+ return ProviderResponse(
369
+ success=False,
370
+ error_message=f"Currencies request error: {str(e)}"
371
+ )
372
+
373
+ def get_supported_networks(self, currency_code: str = None) -> ProviderResponse:
374
+ """Get supported networks from Cryptomus."""
375
+ try:
376
+ headers = self._generate_headers({})
377
+
378
+ # Cryptomus might have a specific API endpoint for networks
379
+ # For now, we'll extract from currencies data
380
+ currencies_response = self.get_supported_currencies()
381
+ if not currencies_response.success:
382
+ return currencies_response
383
+
384
+ networks = {}
385
+ currencies = currencies_response.data.get('currencies', [])
386
+
387
+ for currency in currencies:
388
+ currency_symbol = currency.get('currency_code', '').upper()
389
+ if currency_code and currency_symbol != currency_code.upper():
390
+ continue
391
+
392
+ # Extract network info from currency data
393
+ network_info = {
394
+ 'code': currency.get('network', 'mainnet'),
395
+ 'name': currency.get('network_name', 'Mainnet'),
396
+ 'min_amount': currency.get('min_amount', 0),
397
+ 'max_amount': currency.get('max_amount', 0),
398
+ 'commission_percent': currency.get('commission_percent', 0)
399
+ }
400
+
401
+ if currency_symbol not in networks:
402
+ networks[currency_symbol] = []
403
+ networks[currency_symbol].append(network_info)
404
+
405
+ return ProviderResponse(
406
+ success=True,
407
+ data={'networks': networks}
408
+ )
409
+
410
+ except Exception as e:
411
+ logger.error(f"Cryptomus networks request failed: {e}")
412
+ return ProviderResponse(
413
+ success=False,
414
+ error_message=f"Networks request error: {str(e)}"
415
+ )
416
+
417
+ def get_currency_network_mapping(self) -> Dict[str, List[str]]:
418
+ """Get mapping of currencies to their supported networks."""
419
+ networks_response = self.get_supported_networks()
420
+ if not networks_response.success:
421
+ return {}
422
+
423
+ mapping = {}
424
+ networks_data = networks_response.data.get('networks', {})
425
+
426
+ for currency_code, networks in networks_data.items():
427
+ mapping[currency_code] = [network['code'] for network in networks]
428
+
429
+ return mapping