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