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,564 +0,0 @@
1
- """
2
- Cryptomus payment provider implementation using official library.
3
- Enhanced with better error handling, security, and Pydantic models.
4
- """
5
-
6
- from decimal import Decimal
7
- from typing import Dict, Any, Optional, List
8
- from datetime import datetime
9
-
10
- from cryptomus import Client
11
- from cryptomus.request_exceptions import RequestExceptionsBuilder
12
-
13
- from ..base import PaymentProvider
14
- from ...internal_types import ProviderResponse, WebhookData, PaymentAmountEstimate, ProviderInfo
15
- from .models import CryptomusConfig, CryptomusWebhook
16
- from django_cfg.modules.django_logger import get_logger
17
-
18
- logger = get_logger("cryptomus")
19
-
20
-
21
- class CryptomusProviderV2(PaymentProvider):
22
- """
23
- Enhanced Cryptomus payment provider using official Python library.
24
- Features:
25
- - Official cryptomus library integration
26
- - Proper error handling with RequestExceptionsBuilder
27
- - Pydantic model validation
28
- - Universal field mapping
29
- - Security-first approach
30
- """
31
-
32
- name = "cryptomus_v2"
33
- display_name = "Cryptomus (Official)"
34
-
35
- def __init__(self, config: CryptomusConfig):
36
- super().__init__(config)
37
- self.config = config
38
- self._payment_client = None
39
- self._payout_client = None
40
-
41
- @property
42
- def payment_client(self):
43
- """Lazy-loaded payment client using official library."""
44
- if self._payment_client is None:
45
- try:
46
- self._payment_client = Client.payment(
47
- self.config.api_key,
48
- self.config.merchant_id
49
- )
50
- except Exception as e:
51
- logger.error(f"Failed to initialize Cryptomus payment client: {e}")
52
- raise
53
- return self._payment_client
54
-
55
- @property
56
- def payout_client(self):
57
- """Lazy-loaded payout client using official library."""
58
- if self._payout_client is None and hasattr(self.config, 'payout_api_key'):
59
- try:
60
- self._payout_client = Client.payout(
61
- self.config.payout_api_key,
62
- self.config.merchant_id
63
- )
64
- except Exception as e:
65
- logger.error(f"Failed to initialize Cryptomus payout client: {e}")
66
- # Don't raise here, payout is optional
67
- pass
68
- return self._payout_client
69
-
70
- def create_payment(self, payment_data: dict) -> ProviderResponse:
71
- """
72
- Create payment using official Cryptomus library.
73
- Maps to universal payment fields with enhanced error handling.
74
- """
75
- try:
76
- # Validate required fields
77
- order_id = payment_data.get('order_id')
78
- amount = payment_data.get('amount')
79
- currency = payment_data.get('currency', 'USD')
80
-
81
- if not all([order_id, amount]):
82
- return ProviderResponse(
83
- success=False,
84
- error_message="Missing required fields: order_id, amount"
85
- )
86
-
87
- # Validate amount
88
- try:
89
- amount_decimal = Decimal(str(amount))
90
- if amount_decimal <= 0:
91
- return ProviderResponse(
92
- success=False,
93
- error_message="Amount must be positive"
94
- )
95
- except (ValueError, TypeError):
96
- return ProviderResponse(
97
- success=False,
98
- error_message="Invalid amount format"
99
- )
100
-
101
- # Prepare payment data for Cryptomus API
102
- cryptomus_data = {
103
- 'amount': str(amount_decimal),
104
- 'currency': currency,
105
- 'order_id': order_id,
106
- 'url_callback': payment_data.get('callback_url', self.config.callback_url),
107
- 'url_return': payment_data.get('return_url'),
108
- 'url_success': payment_data.get('success_url', self.config.success_url),
109
- 'url_cancel': payment_data.get('cancel_url', self.config.cancel_url),
110
- 'is_payment_multiple': payment_data.get('allow_overpayment', True),
111
- 'lifetime': payment_data.get('lifetime', 3600), # 1 hour default
112
- 'to_currency': payment_data.get('crypto_currency', 'BTC')
113
- }
114
-
115
- # Add optional fields
116
- if payment_data.get('description'):
117
- cryptomus_data['additional_data'] = {
118
- 'description': payment_data['description']
119
- }
120
-
121
- # Create payment using official library
122
- result = self.payment_client.create(cryptomus_data)
123
-
124
- # Map response to universal format
125
- return ProviderResponse(
126
- success=True,
127
- transaction_id=result.get('uuid'),
128
- provider_payment_id=result.get('uuid'),
129
- pay_address=result.get('address'),
130
- pay_amount=Decimal(str(result.get('payment_amount', 0))),
131
- pay_currency=result.get('payer_currency', currency),
132
- data={
133
- # Universal fields
134
- 'payment_url': result.get('url'),
135
- 'qr_code_data': result.get('url'), # Use payment URL for QR
136
- 'expires_at': result.get('expired_at'),
137
-
138
- # Cryptomus specific
139
- 'cryptomus_uuid': result.get('uuid'),
140
- 'cryptomus_order_id': result.get('order_id'),
141
- 'cryptomus_status': result.get('payment_status'),
142
- 'cryptomus_amount': result.get('amount'),
143
- 'cryptomus_currency': result.get('currency'),
144
- 'cryptomus_network': result.get('network'),
145
- 'cryptomus_address': result.get('address'),
146
- 'cryptomus_is_final': result.get('is_final', False)
147
- }
148
- )
149
-
150
- except RequestExceptionsBuilder as e:
151
- # Handle Cryptomus API exceptions
152
- error_msg = f"Cryptomus API error: {e}"
153
-
154
- # Check for specific error types
155
- if hasattr(e, 'args') and len(e.args) >= 2:
156
- status_code = e.args[1] if isinstance(e.args[1], int) else 0
157
-
158
- if status_code == 400:
159
- if 'balance' in str(e).lower():
160
- error_msg = "Insufficient balance in merchant account"
161
- elif 'amount' in str(e).lower():
162
- error_msg = "Invalid payment amount"
163
- elif 'currency' in str(e).lower():
164
- error_msg = "Unsupported currency"
165
- elif status_code == 401:
166
- error_msg = "Authentication failed: Invalid API key or merchant ID"
167
- elif status_code == 403:
168
- error_msg = "Access denied: Check merchant permissions"
169
- elif status_code == 422:
170
- error_msg = "Validation error: Check request parameters"
171
-
172
- logger.error(f"Cryptomus create_payment error: {error_msg}")
173
- return ProviderResponse(
174
- success=False,
175
- error_message=error_msg
176
- )
177
-
178
- except Exception as e:
179
- logger.error(f"Unexpected error in Cryptomus create_payment: {e}")
180
- return ProviderResponse(
181
- success=False,
182
- error_message=f"Unexpected error: {str(e)}"
183
- )
184
-
185
- def validate_webhook(self, webhook_data: Dict[str, Any],
186
- request_headers: Optional[Dict[str, str]] = None,
187
- raw_body: Optional[bytes] = None) -> bool:
188
- """
189
- Validate Cryptomus webhook using Pydantic model and signature verification.
190
- Enhanced security with strict validation.
191
- """
192
- try:
193
- # Validate using Pydantic model
194
- webhook = CryptomusWebhook(**webhook_data)
195
-
196
- # Verify signature if provided
197
- signature = webhook.sign
198
- if signature:
199
- # Prepare data for signature verification (exclude sign field)
200
- data_for_sign = webhook.model_dump(exclude={'sign'})
201
- expected_signature = self._generate_webhook_signature(data_for_sign)
202
-
203
- if signature != expected_signature:
204
- logger.error("Cryptomus webhook signature verification failed")
205
- return False
206
- else:
207
- logger.warning("Cryptomus webhook missing signature - validation skipped")
208
-
209
- # Additional business logic validation
210
- if webhook.type not in ['payment', 'payout']:
211
- logger.warning(f"Unknown webhook type: {webhook.type}")
212
- return False
213
-
214
- return True
215
-
216
- except Exception as e:
217
- logger.error(f"Cryptomus webhook validation failed: {e}")
218
- return False
219
-
220
- def process_webhook(self, webhook_data: Dict[str, Any]) -> WebhookData:
221
- """
222
- Process Cryptomus webhook with enhanced error handling.
223
- Maps to universal webhook data format.
224
- """
225
- try:
226
- # Validate and parse webhook data
227
- webhook = CryptomusWebhook(**webhook_data)
228
-
229
- # Map status to universal format
230
- universal_status = self._map_status(webhook.status)
231
-
232
- # Calculate amounts
233
- pay_amount = webhook.amount if webhook.amount else Decimal('0')
234
- actually_paid = webhook.payment_amount if webhook.payment_amount else pay_amount
235
-
236
- return WebhookData(
237
- provider_payment_id=webhook.uuid,
238
- status=universal_status,
239
- pay_amount=pay_amount,
240
- pay_currency=webhook.payer_currency or webhook.currency,
241
- actually_paid=actually_paid,
242
- order_id=webhook.order_id,
243
- signature=webhook.txid or webhook.sign,
244
- transaction_hash=webhook.txid,
245
- network=webhook.network,
246
- from_address=webhook.from_address,
247
- is_final=webhook.is_final,
248
- raw_data=webhook_data
249
- )
250
-
251
- except Exception as e:
252
- logger.error(f"Cryptomus webhook processing failed: {e}")
253
- raise
254
-
255
- def check_payment_status(self, payment_id: str) -> ProviderResponse:
256
- """Get payment status using official library."""
257
- try:
258
- # Use info method with UUID
259
- result = self.payment_client.info({'uuid': payment_id})
260
-
261
- return ProviderResponse(
262
- success=True,
263
- provider_payment_id=result.get('uuid'),
264
- status=self._map_status(result.get('payment_status')),
265
- pay_address=result.get('address'),
266
- pay_amount=Decimal(str(result.get('payment_amount', 0))),
267
- pay_currency=result.get('payer_currency'),
268
- data={
269
- 'transaction_hash': result.get('txid'),
270
- 'network': result.get('network'),
271
- 'confirmations': result.get('confirmations', 0),
272
- 'is_final': result.get('is_final', False),
273
- 'created_at': result.get('created_at'),
274
- 'updated_at': result.get('updated_at')
275
- }
276
- )
277
-
278
- except RequestExceptionsBuilder as e:
279
- error_msg = f"Cryptomus payment status error: {e}"
280
- logger.error(error_msg)
281
- return ProviderResponse(
282
- success=False,
283
- error_message=error_msg
284
- )
285
- except Exception as e:
286
- logger.error(f"Unexpected error in get_payment_status: {e}")
287
- return ProviderResponse(
288
- success=False,
289
- error_message=f"Unexpected error: {str(e)}"
290
- )
291
-
292
- def get_supported_currencies(self) -> ProviderResponse:
293
- """Get supported currencies using official library services endpoint."""
294
- try:
295
- services = self.payment_client.services()
296
-
297
- # Extract unique currencies
298
- currencies = list(set([
299
- service.get('currency') for service in services
300
- if service.get('currency')
301
- ]))
302
-
303
- return ProviderResponse(
304
- success=True,
305
- data={
306
- 'currencies': currencies,
307
- 'services': services # Full service data for advanced usage
308
- }
309
- )
310
-
311
- except RequestExceptionsBuilder as e:
312
- error_msg = f"Cryptomus services error: {e}"
313
- logger.error(error_msg)
314
- return ProviderResponse(
315
- success=False,
316
- error_message=error_msg
317
- )
318
- except Exception as e:
319
- logger.error(f"Unexpected error in get_supported_currencies: {e}")
320
- return ProviderResponse(
321
- success=False,
322
- error_message=f"Unexpected error: {str(e)}"
323
- )
324
-
325
- def get_supported_networks(self, currency_code: str = None) -> ProviderResponse:
326
- """Get supported networks from services data."""
327
- try:
328
- services_response = self.get_supported_currencies()
329
- if not services_response.success:
330
- return services_response
331
-
332
- services = services_response.data.get('services', [])
333
- networks = {}
334
-
335
- for service in services:
336
- currency = service.get('currency', '').upper()
337
- network = service.get('network')
338
-
339
- if currency_code and currency != currency_code.upper():
340
- continue
341
-
342
- if currency and network:
343
- if currency not in networks:
344
- networks[currency] = []
345
-
346
- network_info = {
347
- 'code': network,
348
- 'name': service.get('network_name', network),
349
- 'min_amount': service.get('min_amount', 0),
350
- 'max_amount': service.get('max_amount', 0),
351
- 'commission_percent': service.get('commission_percent', 0),
352
- 'is_available': service.get('is_available', True)
353
- }
354
- networks[currency].append(network_info)
355
-
356
- return ProviderResponse(
357
- success=True,
358
- data={'networks': networks}
359
- )
360
-
361
- except Exception as e:
362
- logger.error(f"Error in get_supported_networks: {e}")
363
- return ProviderResponse(
364
- success=False,
365
- error_message=f"Error: {str(e)}"
366
- )
367
-
368
- def get_balance(self) -> ProviderResponse:
369
- """Get merchant balance using official library."""
370
- try:
371
- balance = self.payment_client.balance()
372
-
373
- return ProviderResponse(
374
- success=True,
375
- data={'balance': balance}
376
- )
377
-
378
- except RequestExceptionsBuilder as e:
379
- error_msg = f"Cryptomus balance error: {e}"
380
- logger.error(error_msg)
381
- return ProviderResponse(
382
- success=False,
383
- error_message=error_msg
384
- )
385
- except Exception as e:
386
- logger.error(f"Unexpected error in get_balance: {e}")
387
- return ProviderResponse(
388
- success=False,
389
- error_message=f"Unexpected error: {str(e)}"
390
- )
391
-
392
- def estimate_payment_amount(self, amount: Decimal, currency_code: str) -> Optional[PaymentAmountEstimate]:
393
- """
394
- Estimate payment amount by creating a test payment.
395
- Note: Cryptomus doesn't have a dedicated estimation API.
396
- """
397
- try:
398
- # For estimation, we could use the exchange rate or create a test payment
399
- # Since there's no dedicated estimation API, we'll return a basic estimate
400
- # based on 1:1 ratio and let the actual payment creation determine the real rate
401
-
402
- return PaymentAmountEstimate(
403
- currency_from='usd',
404
- currency_to=currency_code.lower(),
405
- amount_from=amount,
406
- estimated_amount=amount, # 1:1 estimate, real rate determined at payment creation
407
- exchange_rate=Decimal('1.0'),
408
- provider_name=self.name,
409
- estimated_at=datetime.now()
410
- )
411
-
412
- except Exception as e:
413
- logger.error(f"Cryptomus estimate_payment_amount error: {e}")
414
- return None
415
-
416
- def create_wallet(self, currency: str, network: str, order_id: str,
417
- callback_url: str = None) -> ProviderResponse:
418
- """Create static wallet using official library."""
419
- try:
420
- wallet_data = {
421
- 'network': network,
422
- 'currency': currency,
423
- 'order_id': order_id
424
- }
425
-
426
- if callback_url:
427
- wallet_data['url_callback'] = callback_url
428
- elif self.config.callback_url:
429
- wallet_data['url_callback'] = self.config.callback_url
430
-
431
- result = self.payment_client.create_wallet(wallet_data)
432
-
433
- return ProviderResponse(
434
- success=True,
435
- provider_payment_id=result.get('uuid'),
436
- pay_address=result.get('address'),
437
- data={
438
- 'wallet_uuid': result.get('uuid'),
439
- 'address': result.get('address'),
440
- 'currency': currency,
441
- 'network': network,
442
- 'order_id': order_id
443
- }
444
- )
445
-
446
- except RequestExceptionsBuilder as e:
447
- error_msg = f"Cryptomus wallet creation error: {e}"
448
- logger.error(error_msg)
449
- return ProviderResponse(
450
- success=False,
451
- error_message=error_msg
452
- )
453
- except Exception as e:
454
- logger.error(f"Unexpected error in create_wallet: {e}")
455
- return ProviderResponse(
456
- success=False,
457
- error_message=f"Unexpected error: {str(e)}"
458
- )
459
-
460
- def resend_notification(self, payment_id: str = None, order_id: str = None) -> ProviderResponse:
461
- """Resend webhook notification using official library."""
462
- try:
463
- if not payment_id and not order_id:
464
- return ProviderResponse(
465
- success=False,
466
- error_message="Either payment_id (uuid) or order_id must be provided"
467
- )
468
-
469
- data = {}
470
- if payment_id:
471
- data['uuid'] = payment_id
472
- if order_id:
473
- data['order_id'] = order_id
474
-
475
- result = self.payment_client.resend_notification(data)
476
-
477
- return ProviderResponse(
478
- success=True,
479
- data={'resend_result': result}
480
- )
481
-
482
- except RequestExceptionsBuilder as e:
483
- error_msg = f"Cryptomus resend notification error: {e}"
484
- logger.error(error_msg)
485
- return ProviderResponse(
486
- success=False,
487
- error_message=error_msg
488
- )
489
- except Exception as e:
490
- logger.error(f"Unexpected error in resend_notification: {e}")
491
- return ProviderResponse(
492
- success=False,
493
- error_message=f"Unexpected error: {str(e)}"
494
- )
495
-
496
- def _generate_webhook_signature(self, webhook_data: dict) -> str:
497
- """Generate expected webhook signature for validation."""
498
- import json
499
- import base64
500
- import hashlib
501
-
502
- # Cryptomus webhook signature: md5(base64(json_data) + api_key)
503
- json_data = json.dumps(webhook_data, separators=(',', ':'), sort_keys=True)
504
- encoded_data = base64.b64encode(json_data.encode('utf-8')).decode('utf-8')
505
- signature_string = encoded_data + self.config.api_key
506
- return hashlib.md5(signature_string.encode('utf-8')).hexdigest()
507
-
508
- def _map_status(self, cryptomus_status: str) -> str:
509
- """Map Cryptomus status to universal status."""
510
- status_mapping = {
511
- 'check': 'pending',
512
- 'process': 'pending',
513
- 'confirm_check': 'pending',
514
- 'paid': 'completed',
515
- 'paid_over': 'completed',
516
- 'confirmed': 'completed',
517
- 'fail': 'failed',
518
- 'wrong_amount': 'failed',
519
- 'cancel': 'cancelled',
520
- 'system_fail': 'failed',
521
- 'refund_process': 'refunding',
522
- 'refund_fail': 'failed',
523
- 'refund_paid': 'refunded'
524
- }
525
- return status_mapping.get(cryptomus_status, 'pending')
526
-
527
- def get_provider_info(self) -> ProviderInfo:
528
- """Get provider information with Pydantic model."""
529
- return ProviderInfo(
530
- name=self.name,
531
- display_name=self.display_name,
532
- supported_currencies=["BTC", "ETH", "USDT", "USDC", "LTC", "BCH", "TRX", "BNB"],
533
- is_active=self.config.enabled,
534
- features={
535
- "hosted_payments": True,
536
- "static_wallets": True,
537
- "payouts": bool(self.payout_client),
538
- "webhooks": True,
539
- "qr_codes": True,
540
- "overpayments": True,
541
- "refunds": True,
542
- "api_endpoints": {
543
- "create_payment": "https://api.cryptomus.com/v1/payment",
544
- "payment_info": "https://api.cryptomus.com/v1/payment/info",
545
- "services": "https://api.cryptomus.com/v1/payment/services",
546
- "balance": "https://api.cryptomus.com/v1/balance",
547
- "create_wallet": "https://api.cryptomus.com/v1/wallet"
548
- }
549
- }
550
- )
551
-
552
- def get_currency_network_mapping(self) -> Dict[str, List[str]]:
553
- """Get currency to network mapping."""
554
- networks_response = self.get_supported_networks()
555
- if not networks_response.success:
556
- return {}
557
-
558
- mapping = {}
559
- networks_data = networks_response.data.get('networks', {})
560
-
561
- for currency_code, networks in networks_data.items():
562
- mapping[currency_code] = [network['code'] for network in networks]
563
-
564
- return mapping
@@ -1,34 +0,0 @@
1
- """
2
- Provider-specific models package.
3
-
4
- Re-exports provider-specific currency and network models.
5
- Universal service models are in internal_types.py.
6
- """
7
-
8
- from .currencies import (
9
- # Enums
10
- CurrencyType,
11
- NetworkType,
12
-
13
- # Provider-specific currency models
14
- CurrencyInfo,
15
- NetworkInfo,
16
- ProviderCurrencyResponse,
17
- ProviderNetworkResponse,
18
- CurrencyNetworkMapping,
19
- )
20
-
21
- # Monitoring models moved to services/monitoring/api_schemas.py
22
-
23
- __all__ = [
24
- # Enums
25
- 'CurrencyType',
26
- 'NetworkType',
27
-
28
- # Provider currency models
29
- 'CurrencyInfo',
30
- 'NetworkInfo',
31
- 'ProviderCurrencyResponse',
32
- 'ProviderNetworkResponse',
33
- 'CurrencyNetworkMapping',
34
- ]