django-cfg 1.2.31__py3-none-any.whl → 1.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.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,429 +0,0 @@
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