django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,166 @@
1
+ """
2
+ Base service class for the Universal Payment System v2.0.
3
+
4
+ Provides common functionality for all services.
5
+ """
6
+
7
+ from abc import ABC
8
+ from typing import Optional, Dict, Any, Type
9
+ from django.db import transaction
10
+ from django_cfg.modules.django_logger import get_logger
11
+ from ..types import ServiceOperationResult
12
+
13
+
14
+ class BaseService(ABC):
15
+ """
16
+ Base service class with common functionality.
17
+
18
+ Provides logging, error handling, and transaction management.
19
+ """
20
+
21
+ def __init__(self):
22
+ """Initialize base service."""
23
+ self.logger = get_logger(f"services.{self.__class__.__name__.lower()}")
24
+ self._cache = {}
25
+
26
+ # Initialize config service
27
+ from ...config.constance import get_payment_config_service
28
+ self.config_service = get_payment_config_service()
29
+
30
+ def _create_success_result(
31
+ self,
32
+ message: str = "Operation completed successfully",
33
+ data: Optional[Dict[str, Any]] = None
34
+ ) -> ServiceOperationResult:
35
+ """Create success result."""
36
+ return ServiceOperationResult(
37
+ success=True,
38
+ message=message,
39
+ data=data or {}
40
+ )
41
+
42
+ def _create_error_result(
43
+ self,
44
+ message: str,
45
+ error_code: Optional[str] = None,
46
+ data: Optional[Dict[str, Any]] = None
47
+ ) -> ServiceOperationResult:
48
+ """Create error result."""
49
+ return ServiceOperationResult(
50
+ success=False,
51
+ message=message,
52
+ error_code=error_code,
53
+ data=data or {}
54
+ )
55
+
56
+ def _log_operation(
57
+ self,
58
+ operation: str,
59
+ success: bool,
60
+ **kwargs
61
+ ) -> None:
62
+ """Log service operation."""
63
+ log_data = {
64
+ 'service': self.__class__.__name__,
65
+ 'operation': operation,
66
+ 'success': success,
67
+ **kwargs
68
+ }
69
+
70
+ if success:
71
+ self.logger.info(f"Operation {operation} completed successfully", extra=log_data)
72
+ else:
73
+ self.logger.error(f"Operation {operation} failed", extra=log_data)
74
+
75
+ def _handle_exception(
76
+ self,
77
+ operation: str,
78
+ exception: Exception,
79
+ **context
80
+ ) -> ServiceOperationResult:
81
+ """Handle service exception."""
82
+ error_message = f"Service error in {operation}: {str(exception)}"
83
+
84
+ self.logger.error(error_message, extra={
85
+ 'service': self.__class__.__name__,
86
+ 'operation': operation,
87
+ 'exception_type': type(exception).__name__,
88
+ 'exception_message': str(exception),
89
+ **context
90
+ }, exc_info=True)
91
+
92
+ return self._create_error_result(
93
+ message=error_message,
94
+ error_code=type(exception).__name__.lower(),
95
+ data={'context': context}
96
+ )
97
+
98
+ @transaction.atomic
99
+ def _execute_with_transaction(self, operation_func, *args, **kwargs):
100
+ """Execute operation within database transaction."""
101
+ try:
102
+ return operation_func(*args, **kwargs)
103
+ except Exception as e:
104
+ self.logger.error(f"Transaction rolled back due to error: {e}")
105
+ raise
106
+
107
+ def _validate_input(self, data: Any, model_class: Type) -> Any:
108
+ """Validate input data using Pydantic model."""
109
+ try:
110
+ if isinstance(data, dict):
111
+ return model_class(**data)
112
+ elif isinstance(data, model_class):
113
+ return data
114
+ else:
115
+ return model_class.model_validate(data)
116
+ except Exception as e:
117
+ raise ValueError(f"Invalid input data: {e}")
118
+
119
+ def _get_cache_key(self, prefix: str, *args) -> str:
120
+ """Generate cache key."""
121
+ key_parts = [prefix] + [str(arg) for arg in args]
122
+ return ":".join(key_parts)
123
+
124
+ def _cache_get(self, key: str) -> Optional[Any]:
125
+ """Get value from cache."""
126
+ return self._cache.get(key)
127
+
128
+ def _cache_set(self, key: str, value: Any, ttl: int = 300) -> None:
129
+ """Set value in cache."""
130
+ # Simple in-memory cache for now
131
+ # In production, this would use Redis
132
+ self._cache[key] = value
133
+
134
+ def _cache_delete(self, key: str) -> None:
135
+ """Delete value from cache."""
136
+ self._cache.pop(key, None)
137
+
138
+ def _cache_clear(self, prefix: Optional[str] = None) -> None:
139
+ """Clear cache entries."""
140
+ if prefix:
141
+ keys_to_delete = [k for k in self._cache.keys() if k.startswith(prefix)]
142
+ for key in keys_to_delete:
143
+ del self._cache[key]
144
+ else:
145
+ self._cache.clear()
146
+
147
+ def get_service_stats(self) -> Dict[str, Any]:
148
+ """Get service statistics."""
149
+ return {
150
+ 'service_name': self.__class__.__name__,
151
+ 'cache_size': len(self._cache),
152
+ 'cache_keys': list(self._cache.keys())
153
+ }
154
+
155
+ def health_check(self) -> ServiceOperationResult:
156
+ """Perform service health check."""
157
+ try:
158
+ # Basic health check - can be overridden by subclasses
159
+ stats = self.get_service_stats()
160
+
161
+ return self._create_success_result(
162
+ message=f"{self.__class__.__name__} is healthy",
163
+ data=stats
164
+ )
165
+ except Exception as e:
166
+ return self._handle_exception("health_check", e)
@@ -0,0 +1,478 @@
1
+ """
2
+ Currency service for the Universal Payment System v2.0.
3
+
4
+ Handles currency conversion and rate management using django_currency module.
5
+ """
6
+
7
+ from typing import Optional, Dict, Any, List
8
+ from decimal import Decimal
9
+ from django.utils import timezone
10
+ from datetime import timedelta
11
+
12
+ from .base import BaseService
13
+ from ..types import (
14
+ CurrencyConversionRequest, CurrencyConversionResult, CurrencyData,
15
+ ServiceOperationResult
16
+ )
17
+ from ...models import Currency, ProviderCurrency, Network
18
+ from django_cfg.modules.django_currency import (
19
+ convert_currency, get_exchange_rate, CurrencyError
20
+ )
21
+
22
+
23
+ class CurrencyService(BaseService):
24
+ """
25
+ Currency service with conversion and rate management.
26
+
27
+ Integrates with django_currency module for real-time rates.
28
+ """
29
+
30
+ def convert_currency(self, request: CurrencyConversionRequest) -> CurrencyConversionResult:
31
+ """
32
+ Convert amount between currencies.
33
+
34
+ Args:
35
+ request: Currency conversion request
36
+
37
+ Returns:
38
+ CurrencyConversionResult: Conversion result with rate
39
+ """
40
+ try:
41
+ # Validate request
42
+ if isinstance(request, dict):
43
+ request = CurrencyConversionRequest(**request)
44
+
45
+ self.logger.debug("Converting currency", extra={
46
+ 'amount': request.amount,
47
+ 'from_currency': request.from_currency,
48
+ 'to_currency': request.to_currency
49
+ })
50
+
51
+ # Check if currencies are supported
52
+ validation_result = self._validate_currencies(
53
+ request.from_currency,
54
+ request.to_currency
55
+ )
56
+ if not validation_result.success:
57
+ return CurrencyConversionResult(
58
+ success=False,
59
+ message=validation_result.message,
60
+ error_code=validation_result.error_code
61
+ )
62
+
63
+ # Perform conversion using django_currency
64
+ try:
65
+ converted_amount = convert_currency(
66
+ request.amount,
67
+ request.from_currency,
68
+ request.to_currency
69
+ )
70
+
71
+ exchange_rate = get_exchange_rate(
72
+ request.from_currency,
73
+ request.to_currency
74
+ )
75
+
76
+ self._log_operation(
77
+ "convert_currency",
78
+ True,
79
+ from_currency=request.from_currency,
80
+ to_currency=request.to_currency,
81
+ amount=request.amount,
82
+ converted_amount=converted_amount,
83
+ exchange_rate=exchange_rate
84
+ )
85
+
86
+ return CurrencyConversionResult(
87
+ success=True,
88
+ message="Currency converted successfully",
89
+ amount=request.amount,
90
+ from_currency=request.from_currency,
91
+ to_currency=request.to_currency,
92
+ converted_amount=converted_amount,
93
+ exchange_rate=exchange_rate,
94
+ rate_timestamp=timezone.now()
95
+ )
96
+
97
+ except CurrencyError as e:
98
+ return CurrencyConversionResult(
99
+ success=False,
100
+ message=f"Currency conversion failed: {e}",
101
+ error_code="conversion_failed",
102
+ amount=request.amount,
103
+ from_currency=request.from_currency,
104
+ to_currency=request.to_currency
105
+ )
106
+
107
+ except Exception as e:
108
+ return CurrencyConversionResult(**self._handle_exception(
109
+ "convert_currency", e,
110
+ from_currency=request.from_currency if hasattr(request, 'from_currency') else None,
111
+ to_currency=request.to_currency if hasattr(request, 'to_currency') else None
112
+ ).model_dump())
113
+
114
+ def get_exchange_rate(self, base_currency: str, quote_currency: str) -> ServiceOperationResult:
115
+ """
116
+ Get current exchange rate between currencies.
117
+
118
+ Args:
119
+ base_currency: Base currency code
120
+ quote_currency: Quote currency code
121
+
122
+ Returns:
123
+ ServiceOperationResult: Exchange rate information
124
+ """
125
+ try:
126
+ self.logger.debug("Getting exchange rate", extra={
127
+ 'base_currency': base_currency,
128
+ 'quote_currency': quote_currency
129
+ })
130
+
131
+ # Validate currencies
132
+ validation_result = self._validate_currencies(base_currency, quote_currency)
133
+ if not validation_result.success:
134
+ return validation_result
135
+
136
+ # Get rate using django_currency
137
+ try:
138
+ rate = get_exchange_rate(base_currency, quote_currency)
139
+
140
+ return self._create_success_result(
141
+ "Exchange rate retrieved successfully",
142
+ {
143
+ 'base_currency': base_currency,
144
+ 'quote_currency': quote_currency,
145
+ 'exchange_rate': rate,
146
+ 'rate_timestamp': timezone.now().isoformat(),
147
+ 'pair': f"{base_currency}/{quote_currency}"
148
+ }
149
+ )
150
+
151
+ except CurrencyError as e:
152
+ return self._create_error_result(
153
+ f"Failed to get exchange rate: {e}",
154
+ "rate_fetch_failed"
155
+ )
156
+
157
+ except Exception as e:
158
+ return self._handle_exception(
159
+ "get_exchange_rate", e,
160
+ base_currency=base_currency,
161
+ quote_currency=quote_currency
162
+ )
163
+
164
+ def get_supported_currencies(self, provider: Optional[str] = None) -> ServiceOperationResult:
165
+ """
166
+ Get list of supported currencies.
167
+
168
+ Args:
169
+ provider: Filter by provider (optional)
170
+
171
+ Returns:
172
+ ServiceOperationResult: List of supported currencies
173
+ """
174
+ try:
175
+ self.logger.debug("Getting supported currencies", extra={
176
+ 'provider': provider
177
+ })
178
+
179
+ # Get currencies from database
180
+ queryset = Currency.objects.filter(is_enabled=True)
181
+
182
+ if provider:
183
+ # Filter by provider support
184
+ queryset = queryset.filter(
185
+ providercurrency__provider=provider,
186
+ providercurrency__is_enabled=True
187
+ ).distinct()
188
+
189
+ currencies = queryset.order_by('code')
190
+
191
+ # Convert to data
192
+ currency_data = []
193
+ for currency in currencies:
194
+ data = CurrencyData.model_validate(currency)
195
+ currency_info = data.model_dump()
196
+
197
+ # Add provider-specific info if requested
198
+ if provider:
199
+ try:
200
+ provider_currency = ProviderCurrency.objects.get(
201
+ currency=currency,
202
+ provider=provider,
203
+ is_enabled=True
204
+ )
205
+ currency_info['provider_info'] = {
206
+ 'min_amount': float(provider_currency.min_amount) if provider_currency.min_amount else None,
207
+ 'max_amount': float(provider_currency.max_amount) if provider_currency.max_amount else None,
208
+ 'network_fee': float(provider_currency.network_fee) if provider_currency.network_fee else None,
209
+ 'confirmation_blocks': provider_currency.confirmation_blocks
210
+ }
211
+ except ProviderCurrency.DoesNotExist:
212
+ pass
213
+
214
+ currency_data.append(currency_info)
215
+
216
+ return self._create_success_result(
217
+ f"Retrieved {len(currency_data)} supported currencies",
218
+ {
219
+ 'currencies': currency_data,
220
+ 'count': len(currency_data),
221
+ 'provider': provider
222
+ }
223
+ )
224
+
225
+ except Exception as e:
226
+ return self._handle_exception(
227
+ "get_supported_currencies", e,
228
+ provider=provider
229
+ )
230
+
231
+ def get_currency_networks(self, currency_code: str) -> ServiceOperationResult:
232
+ """
233
+ Get available networks for a currency.
234
+
235
+ Args:
236
+ currency_code: Currency code
237
+
238
+ Returns:
239
+ ServiceOperationResult: Available networks
240
+ """
241
+ try:
242
+ self.logger.debug("Getting currency networks", extra={
243
+ 'currency_code': currency_code
244
+ })
245
+
246
+ # Get currency
247
+ try:
248
+ currency = Currency.objects.get(code=currency_code, is_enabled=True)
249
+ except Currency.DoesNotExist:
250
+ return self._create_error_result(
251
+ f"Currency {currency_code} not found or disabled",
252
+ "currency_not_found"
253
+ )
254
+
255
+ # Get networks
256
+ networks = Network.objects.filter(
257
+ currency_code=currency_code,
258
+ is_enabled=True
259
+ ).order_by('name')
260
+
261
+ network_data = []
262
+ for network in networks:
263
+ network_info = {
264
+ 'code': network.code,
265
+ 'name': network.name,
266
+ 'currency_code': network.currency_code,
267
+ 'is_testnet': network.is_testnet,
268
+ 'confirmation_blocks': network.confirmation_blocks,
269
+ 'block_time_seconds': network.block_time_seconds,
270
+ 'estimated_confirmation_time': network.estimated_confirmation_time()
271
+ }
272
+ network_data.append(network_info)
273
+
274
+ return self._create_success_result(
275
+ f"Retrieved {len(network_data)} networks for {currency_code}",
276
+ {
277
+ 'currency_code': currency_code,
278
+ 'networks': network_data,
279
+ 'count': len(network_data)
280
+ }
281
+ )
282
+
283
+ except Exception as e:
284
+ return self._handle_exception(
285
+ "get_currency_networks", e,
286
+ currency_code=currency_code
287
+ )
288
+
289
+ def update_currency_rates(self, currency_codes: Optional[List[str]] = None) -> ServiceOperationResult:
290
+ """
291
+ Update currency rates from external sources.
292
+
293
+ Args:
294
+ currency_codes: Specific currencies to update (optional)
295
+
296
+ Returns:
297
+ ServiceOperationResult: Update result
298
+ """
299
+ try:
300
+ self.logger.info("Updating currency rates", extra={
301
+ 'currency_codes': currency_codes
302
+ })
303
+
304
+ # Get currencies to update
305
+ if currency_codes:
306
+ currencies = Currency.objects.filter(
307
+ code__in=currency_codes,
308
+ is_enabled=True
309
+ )
310
+ else:
311
+ currencies = Currency.objects.filter(is_enabled=True)
312
+
313
+ updated_count = 0
314
+ failed_count = 0
315
+ errors = []
316
+
317
+ # Update rates for each currency against USD
318
+ for currency in currencies:
319
+ try:
320
+ if currency.code != 'USD':
321
+ # Test rate fetch
322
+ rate = get_exchange_rate('USD', currency.code)
323
+ updated_count += 1
324
+
325
+ self.logger.debug(f"Updated rate for {currency.code}", extra={
326
+ 'currency_code': currency.code,
327
+ 'usd_rate': rate
328
+ })
329
+ except CurrencyError as e:
330
+ failed_count += 1
331
+ error_msg = f"{currency.code}: {str(e)}"
332
+ errors.append(error_msg)
333
+
334
+ self.logger.warning(f"Failed to update rate for {currency.code}", extra={
335
+ 'currency_code': currency.code,
336
+ 'error': str(e)
337
+ })
338
+
339
+ self._log_operation(
340
+ "update_currency_rates",
341
+ failed_count == 0,
342
+ updated_count=updated_count,
343
+ failed_count=failed_count
344
+ )
345
+
346
+ return self._create_success_result(
347
+ f"Updated rates for {updated_count} currencies, {failed_count} failed",
348
+ {
349
+ 'updated_count': updated_count,
350
+ 'failed_count': failed_count,
351
+ 'errors': errors,
352
+ 'total_currencies': currencies.count()
353
+ }
354
+ )
355
+
356
+ except Exception as e:
357
+ return self._handle_exception(
358
+ "update_currency_rates", e,
359
+ currency_codes=currency_codes
360
+ )
361
+
362
+ def get_currency_stats(self) -> ServiceOperationResult:
363
+ """
364
+ Get currency statistics.
365
+
366
+ Returns:
367
+ ServiceOperationResult: Currency statistics
368
+ """
369
+ try:
370
+ # Currency counts
371
+ total_currencies = Currency.objects.count()
372
+ enabled_currencies = Currency.objects.filter(is_enabled=True).count()
373
+ crypto_currencies = Currency.objects.filter(
374
+ currency_type=Currency.CurrencyType.CRYPTO,
375
+ is_enabled=True
376
+ ).count()
377
+ fiat_currencies = Currency.objects.filter(
378
+ currency_type=Currency.CurrencyType.FIAT,
379
+ is_enabled=True
380
+ ).count()
381
+
382
+ # Provider support
383
+ provider_stats = ProviderCurrency.objects.filter(
384
+ is_enabled=True
385
+ ).values('provider').annotate(
386
+ currency_count=models.Count('currency', distinct=True)
387
+ ).order_by('-currency_count')
388
+
389
+ # Network stats
390
+ network_stats = Network.objects.filter(
391
+ is_enabled=True
392
+ ).values('currency_code').annotate(
393
+ network_count=models.Count('id')
394
+ ).order_by('-network_count')
395
+
396
+ stats = {
397
+ 'total_currencies': total_currencies,
398
+ 'enabled_currencies': enabled_currencies,
399
+ 'crypto_currencies': crypto_currencies,
400
+ 'fiat_currencies': fiat_currencies,
401
+ 'provider_support': list(provider_stats),
402
+ 'network_support': list(network_stats),
403
+ 'generated_at': timezone.now().isoformat()
404
+ }
405
+
406
+ return self._create_success_result(
407
+ "Currency statistics retrieved",
408
+ stats
409
+ )
410
+
411
+ except Exception as e:
412
+ return self._handle_exception("get_currency_stats", e)
413
+
414
+ def _validate_currencies(self, from_currency: str, to_currency: str) -> ServiceOperationResult:
415
+ """Validate that currencies are supported."""
416
+ try:
417
+ # Check from_currency
418
+ if not Currency.objects.filter(code=from_currency, is_enabled=True).exists():
419
+ return self._create_error_result(
420
+ f"Currency {from_currency} not supported",
421
+ "from_currency_not_supported"
422
+ )
423
+
424
+ # Check to_currency
425
+ if not Currency.objects.filter(code=to_currency, is_enabled=True).exists():
426
+ return self._create_error_result(
427
+ f"Currency {to_currency} not supported",
428
+ "to_currency_not_supported"
429
+ )
430
+
431
+ return self._create_success_result("Currencies are valid")
432
+
433
+ except Exception as e:
434
+ return self._create_error_result(
435
+ f"Currency validation error: {e}",
436
+ "validation_error"
437
+ )
438
+
439
+ def health_check(self) -> ServiceOperationResult:
440
+ """Perform currency service health check."""
441
+ try:
442
+ # Check database connectivity
443
+ currency_count = Currency.objects.filter(is_enabled=True).count()
444
+
445
+ # Test currency conversion
446
+ try:
447
+ test_rate = get_exchange_rate('USD', 'BTC')
448
+ conversion_healthy = True
449
+ except CurrencyError:
450
+ conversion_healthy = False
451
+
452
+ # Check provider currencies
453
+ provider_currency_count = ProviderCurrency.objects.filter(
454
+ is_enabled=True
455
+ ).count()
456
+
457
+ stats = {
458
+ 'service_name': 'CurrencyService',
459
+ 'enabled_currencies': currency_count,
460
+ 'provider_currencies': provider_currency_count,
461
+ 'conversion_service_healthy': conversion_healthy,
462
+ 'django_currency_module': 'available'
463
+ }
464
+
465
+ if conversion_healthy and currency_count > 0:
466
+ return self._create_success_result(
467
+ "CurrencyService is healthy",
468
+ stats
469
+ )
470
+ else:
471
+ return self._create_error_result(
472
+ "CurrencyService has issues",
473
+ "service_unhealthy",
474
+ stats
475
+ )
476
+
477
+ except Exception as e:
478
+ return self._handle_exception("health_check", e)