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
@@ -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)