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,386 +1,452 @@
1
1
  """
2
- Provider registry for managing payment providers.
2
+ Provider registry for the Universal Payment System v2.0.
3
3
 
4
- Central registry with lazy loading and typed configuration.
4
+ Centralized management of payment providers with health monitoring and fallbacks.
5
5
  """
6
6
 
7
- import time
8
- from typing import Optional, List, Dict, Any
9
- from datetime import datetime, timedelta
10
- from django.core.cache import cache
11
- from django.utils import timezone
12
-
13
- from .base import PaymentProvider
14
- from ...utils.config_utils import get_payments_config
15
- from .nowpayments.provider import NowPaymentsProvider
16
- from .nowpayments.models import NowPaymentsConfig
17
- from .cryptapi.provider import CryptAPIProvider
18
- from .cryptapi.models import CryptAPIConfig
19
- from .cryptomus.provider import CryptomusProvider
20
- from .cryptomus.models import CryptomusConfig
21
- from .stripe.provider import StripeProvider
22
- from .stripe.models import StripeConfig
7
+ from enum import Enum
8
+ from typing import Dict, List, Optional, Type, Any
23
9
  from django_cfg.modules.django_logger import get_logger
10
+ # ConfigService removed - using direct Constance access
11
+ from ..types import ServiceOperationResult
12
+ from .base import BaseProvider, ProviderConfig
13
+ from .nowpayments import NowPaymentsProvider, NowPaymentsConfig
24
14
 
25
15
  logger = get_logger("provider_registry")
26
16
 
27
17
 
18
+ # Provider enums
19
+ class ProviderEnum(Enum):
20
+ NOWPAYMENTS = "nowpayments"
21
+ CRYPTAPI = "cryptapi"
22
+ STRIPE = "stripe"
23
+ CRYPTOMUS = "cryptomus"
24
+
25
+ @classmethod
26
+ def get_crypto_providers(cls):
27
+ """Get list of crypto provider values."""
28
+ return [cls.NOWPAYMENTS.value, cls.CRYPTAPI.value, cls.CRYPTOMUS.value]
29
+
30
+ @classmethod
31
+ def get_fiat_providers(cls):
32
+ """Get list of fiat provider values."""
33
+ return [cls.STRIPE.value]
34
+
35
+ @classmethod
36
+ def get_priority_order(cls):
37
+ """Get list of provider values in priority order."""
38
+ return [
39
+ cls.NOWPAYMENTS.value,
40
+ cls.CRYPTAPI.value,
41
+ cls.STRIPE.value,
42
+ cls.CRYPTOMUS.value
43
+ ]
44
+
28
45
  class ProviderRegistry:
29
- """Central registry for payment providers with typed configs."""
46
+ """
47
+ Registry for managing payment providers.
48
+
49
+ Provides centralized access to providers with health monitoring,
50
+ configuration management, and fallback mechanisms.
51
+ """
30
52
 
31
53
  def __init__(self):
32
- """Initialize registry with lazy loading and health monitoring."""
33
- self._providers: dict[str, PaymentProvider] = {}
34
- self._provider_configs: dict[str, dict] = {}
35
- self._health_cache: dict[str, dict] = {}
36
- self._fallback_order: List[str] = [] # Provider preference order
37
- self._load_configurations()
38
- self._initialize_health_monitoring()
54
+ """Initialize provider registry."""
55
+ # Use PaymentConfigService for configuration
56
+ from ...config.constance import get_payment_config_service
57
+
58
+ self.config_service = get_payment_config_service()
59
+ self._providers: Dict[str, BaseProvider] = {}
60
+
61
+ self._provider_classes: Dict[str, Type[BaseProvider]] = {
62
+ ProviderEnum.NOWPAYMENTS.value: NowPaymentsProvider,
63
+ }
64
+ self._provider_configs: Dict[str, Type[ProviderConfig]] = {
65
+ ProviderEnum.NOWPAYMENTS.value: NowPaymentsConfig,
66
+ }
67
+
68
+ self._health_status: Dict[str, bool] = {}
69
+ self._initialized = False
39
70
 
40
- def _load_configurations(self) -> None:
41
- """Load provider configurations."""
71
+ def initialize(self) -> ServiceOperationResult:
72
+ """
73
+ Initialize all configured providers.
74
+
75
+ Returns:
76
+ ServiceOperationResult: Initialization result
77
+ """
42
78
  try:
43
- config = get_payments_config()
79
+ logger.info("Initializing provider registry")
44
80
 
45
- self._provider_configs = {}
46
- for provider_name, provider_config in config.providers.items():
47
- if provider_config.enabled:
48
- self._provider_configs[provider_name] = provider_config.get_config_dict()
49
-
50
- except Exception as e:
51
- logger.warning(f"Failed to load provider configurations: {e}")
52
- self._provider_configs = {}
53
-
54
- def _create_provider(self, name: str, config_dict: dict) -> Optional[PaymentProvider]:
55
- """Create provider instance from configuration with typed config."""
56
- try:
57
- if name == 'nowpayments':
58
- config = NowPaymentsConfig(**config_dict)
59
- return NowPaymentsProvider(config)
60
- elif name == 'cryptapi':
61
- config = CryptAPIConfig(**config_dict)
62
- return CryptAPIProvider(config)
63
- elif name == 'cryptomus':
64
- config = CryptomusConfig(**config_dict)
65
- return CryptomusProvider(config)
66
- elif name == 'stripe':
67
- config = StripeConfig(**config_dict)
68
- return StripeProvider(config)
69
- else:
70
- logger.warning(f"Unknown provider type: {name}")
71
- return None
81
+ # Get all provider configurations
82
+ try:
83
+ provider_configs = self.config_service.get_all_provider_configs()
84
+ except Exception as e:
85
+ return ServiceOperationResult(
86
+ success=False,
87
+ message=f"Failed to get provider configurations: {e}",
88
+ error_code="config_failed"
89
+ )
90
+
91
+ # provider_configs is already a dict from get_all_provider_configs()
92
+ initialized_count = 0
93
+ failed_providers = []
94
+
95
+ # Initialize each configured provider
96
+ for provider_name, config_data in provider_configs.items():
97
+ if not config_data.get('enabled', False):
98
+ logger.debug(f"Skipping disabled provider: {provider_name}")
99
+ continue
72
100
 
73
- except Exception as e:
74
- logger.error(f"Failed to create provider {name}: {e}")
75
- return None
76
-
77
- def register_provider(self, name: str, provider: PaymentProvider) -> None:
78
- """Register a payment provider instance."""
79
- self._providers[name] = provider
80
-
81
- def get_provider(self, name: str) -> Optional[PaymentProvider]:
82
- """Get provider by name with lazy loading."""
83
- # Check if already loaded
84
- if name in self._providers:
85
- return self._providers[name]
86
-
87
- # Try to load from configuration
88
- if name in self._provider_configs:
89
- provider = self._create_provider(name, self._provider_configs[name])
90
- if provider:
91
- self._providers[name] = provider
92
- return provider
93
-
94
- return None
95
-
96
- def list_providers(self) -> List[str]:
97
- """Get list of available providers."""
98
- available = set(self._providers.keys())
99
- available.update(self._provider_configs.keys())
100
- return list(available)
101
-
102
- def get_active_providers(self) -> List[str]:
103
- """Get list of active providers."""
104
- active = []
105
- for name in self.list_providers():
106
- provider = self.get_provider(name)
107
- if provider and provider.enabled:
108
- active.append(name)
109
- return active
110
-
111
- def _initialize_health_monitoring(self) -> None:
112
- """Initialize health monitoring for all providers."""
113
- try:
114
- # Set up fallback order based on configuration or defaults
115
- available_providers = self.list_providers()
101
+ try:
102
+ provider = self._create_provider(provider_name, config_data)
103
+ if provider:
104
+ self._providers[provider_name] = provider
105
+ initialized_count += 1
106
+ logger.info(f"Initialized provider: {provider_name}")
107
+ else:
108
+ failed_providers.append(provider_name)
109
+
110
+ except Exception as e:
111
+ logger.error(f"Failed to initialize provider {provider_name}: {e}")
112
+ failed_providers.append(provider_name)
116
113
 
117
- # Default priority: NowPayments -> CryptAPI -> Cryptomus -> Stripe
118
- priority_order = ['nowpayments', 'cryptapi', 'cryptomus', 'stripe']
114
+ self._initialized = True
119
115
 
120
- self._fallback_order = [p for p in priority_order if p in available_providers]
116
+ # Perform initial health check
117
+ self.health_check_all()
121
118
 
122
- # Add any other providers not in priority list
123
- for provider in available_providers:
124
- if provider not in self._fallback_order:
125
- self._fallback_order.append(provider)
126
-
127
- logger.info(f"Initialized provider fallback order: {self._fallback_order}")
119
+ result_message = f"Initialized {initialized_count} providers"
120
+ if failed_providers:
121
+ result_message += f", failed: {', '.join(failed_providers)}"
122
+
123
+ return ServiceOperationResult(
124
+ success=True,
125
+ message=result_message,
126
+ data={
127
+ 'initialized_providers': list(self._providers.keys()),
128
+ 'failed_providers': failed_providers,
129
+ 'total_configured': len(provider_configs),
130
+ 'initialized_count': initialized_count
131
+ }
132
+ )
128
133
 
129
134
  except Exception as e:
130
- logger.error(f"Error initializing health monitoring: {e}")
131
- self._fallback_order = []
135
+ logger.error(f"Provider registry initialization failed: {e}")
136
+ return ServiceOperationResult(
137
+ success=False,
138
+ message=f"Registry initialization failed: {e}",
139
+ error_code="initialization_failed"
140
+ )
132
141
 
133
- async def health_check_all(self) -> Dict[str, Dict[str, Any]]:
142
+ def get_provider(self, provider_name: str) -> Optional[BaseProvider]:
134
143
  """
135
- Check health of all providers with performance metrics.
144
+ Get provider by name.
136
145
 
146
+ Args:
147
+ provider_name: Provider name
148
+
137
149
  Returns:
138
- Dict mapping provider names to health status
150
+ Optional[BaseProvider]: Provider instance or None
139
151
  """
140
- results = {}
152
+ if not self._initialized:
153
+ logger.warning("Registry not initialized, initializing now")
154
+ self.initialize()
141
155
 
142
- for provider_name in self.list_providers():
143
- try:
144
- provider = self.get_provider(provider_name)
145
- if not provider:
146
- results[provider_name] = {
147
- 'status': 'unavailable',
148
- 'error': 'Provider not loaded',
149
- 'last_check': timezone.now().isoformat()
150
- }
151
- continue
152
-
153
- # Measure response time
154
- start_time = time.time()
155
-
156
- try:
157
- # Use get_supported_currencies as health check endpoint
158
- health_response = provider.get_supported_currencies()
159
- response_time = int((time.time() - start_time) * 1000) # ms
160
-
161
- if health_response.success:
162
- status = 'healthy'
163
- error = None
164
- else:
165
- status = 'degraded'
166
- error = health_response.error_message
167
-
168
- except Exception as provider_error:
169
- response_time = int((time.time() - start_time) * 1000) # ms
170
- status = 'unhealthy'
171
- error = str(provider_error)
172
-
173
- # Cache health status
174
- health_data = {
175
- 'status': status,
176
- 'response_time_ms': response_time,
177
- 'error': error,
178
- 'last_check': timezone.now().isoformat(),
179
- 'provider_enabled': provider.enabled
180
- }
181
-
182
- # Store in cache for 5 minutes
183
- cache_key = f"provider_health:{provider_name}"
184
- cache.set(cache_key, health_data, timeout=300)
185
-
186
- results[provider_name] = health_data
187
-
188
- except Exception as e:
189
- logger.error(f"Health check failed for {provider_name}: {e}")
190
- results[provider_name] = {
191
- 'status': 'error',
192
- 'error': str(e),
193
- 'last_check': timezone.now().isoformat()
194
- }
156
+ provider = self._providers.get(provider_name)
157
+ if not provider:
158
+ logger.warning(f"Provider not found: {provider_name}")
159
+ return None
195
160
 
196
- return results
161
+ # Check if provider is healthy
162
+ if not self._health_status.get(provider_name, False):
163
+ logger.warning(f"Provider {provider_name} is unhealthy")
164
+
165
+ return provider
197
166
 
198
- def get_healthy_providers(self, operation: str = None) -> List[str]:
167
+ def get_available_providers(self) -> List[str]:
199
168
  """
200
- Get list of healthy providers in fallback order.
169
+ Get list of available (healthy) providers.
201
170
 
202
- Args:
203
- operation: Specific operation (e.g., 'payment_creation', 'webhook')
204
-
205
171
  Returns:
206
- List of provider names sorted by health and priority
172
+ List[str]: List of available provider names
207
173
  """
208
- healthy_providers = []
174
+ if not self._initialized:
175
+ self.initialize()
209
176
 
210
- for provider_name in self._fallback_order:
211
- # Check cached health status
212
- cache_key = f"provider_health:{provider_name}"
213
- health_data = cache.get(cache_key)
214
-
215
- if health_data and health_data.get('status') in ['healthy', 'degraded']:
216
- provider = self.get_provider(provider_name)
217
- if provider and provider.enabled:
218
- healthy_providers.append(provider_name)
177
+ return [
178
+ name for name, provider in self._providers.items()
179
+ if self._health_status.get(name, False)
180
+ ]
181
+
182
+ def get_primary_provider(self) -> Optional[BaseProvider]:
183
+ """
184
+ Get primary (preferred) provider.
185
+
186
+ Returns:
187
+ Optional[BaseProvider]: Primary provider or None
188
+ """
189
+ available_providers = self.get_available_providers()
190
+
191
+ if not available_providers:
192
+ logger.warning("No healthy providers available")
193
+ return None
194
+
195
+ # Priority order: nowpayments first, then others
196
+ priority_order = ProviderEnum.get_priority_order()
219
197
 
220
- return healthy_providers
198
+ for provider_name in priority_order:
199
+ if provider_name in available_providers:
200
+ return self._providers[provider_name]
201
+
202
+ # Fallback to first available
203
+ return self._providers[available_providers[0]]
221
204
 
222
- def get_provider_with_fallback(self, preferred_provider: str = None, operation: str = None) -> Optional[PaymentProvider]:
205
+ def get_provider_for_currency(self, currency_code: str) -> Optional[BaseProvider]:
223
206
  """
224
- Get provider with automatic fallback to healthy alternatives.
207
+ Get best provider for specific currency.
225
208
 
226
209
  Args:
227
- preferred_provider: Preferred provider name
228
- operation: Operation type for provider selection
210
+ currency_code: Currency code
229
211
 
230
212
  Returns:
231
- PaymentProvider instance or None if all providers are down
213
+ Optional[BaseProvider]: Best provider for currency or None
232
214
  """
233
- # Start with preferred provider if specified and healthy
234
- if preferred_provider:
235
- provider = self.get_provider(preferred_provider)
236
- if provider and provider.enabled:
237
- # Quick health check from cache
238
- cache_key = f"provider_health:{preferred_provider}"
239
- health_data = cache.get(cache_key)
240
-
241
- if not health_data or health_data.get('status') in ['healthy', 'degraded']:
242
- logger.info(f"Using preferred provider: {preferred_provider}")
243
- return provider
244
- else:
245
- logger.warning(f"Preferred provider {preferred_provider} is unhealthy, falling back")
215
+ available_providers = self.get_available_providers()
246
216
 
247
- # Fallback to healthy providers in order
248
- healthy_providers = self.get_healthy_providers(operation)
217
+ # Find providers that support the currency
218
+ supporting_providers = []
219
+ for provider_name in available_providers:
220
+ provider = self._providers[provider_name]
221
+ if currency_code in provider.config.supported_currencies:
222
+ supporting_providers.append(provider)
249
223
 
250
- for provider_name in healthy_providers:
251
- provider = self.get_provider(provider_name)
252
- if provider:
253
- logger.info(f"Using fallback provider: {provider_name}")
254
- return provider
224
+ if not supporting_providers:
225
+ logger.warning(f"No providers support currency: {currency_code}")
226
+ return None
255
227
 
256
- logger.error("No healthy providers available!")
257
- return None
228
+ # Return first supporting provider (could be enhanced with more logic)
229
+ return supporting_providers[0]
258
230
 
259
- def record_provider_performance(self, provider_name: str, operation: str,
260
- response_time_ms: int, success: bool) -> None:
231
+ def health_check_all(self) -> ServiceOperationResult:
261
232
  """
262
- Record provider performance metrics.
233
+ Perform health check on all providers.
263
234
 
264
- Args:
265
- provider_name: Name of the provider
266
- operation: Operation performed (e.g., 'create_payment', 'check_status')
267
- response_time_ms: Response time in milliseconds
268
- success: Whether operation was successful
235
+ Returns:
236
+ ServiceOperationResult: Overall health status
269
237
  """
270
238
  try:
271
- # Store performance metrics in cache
272
- metric_key = f"provider_metrics:{provider_name}:{operation}"
239
+ logger.debug("Performing health check on all providers")
240
+
241
+ if not self._providers:
242
+ return ServiceOperationResult(
243
+ success=False,
244
+ message="No providers initialized",
245
+ error_code="no_providers"
246
+ )
247
+
248
+ health_results = {}
249
+ healthy_count = 0
250
+
251
+ for provider_name, provider in self._providers.items():
252
+ try:
253
+ health_result = provider.health_check()
254
+ is_healthy = health_result.success
255
+
256
+ self._health_status[provider_name] = is_healthy
257
+ health_results[provider_name] = {
258
+ 'healthy': is_healthy,
259
+ 'message': health_result.message,
260
+ 'data': health_result.data
261
+ }
262
+
263
+ if is_healthy:
264
+ healthy_count += 1
265
+
266
+ logger.debug(f"Provider {provider_name} health: {is_healthy}")
267
+
268
+ except Exception as e:
269
+ logger.error(f"Health check failed for {provider_name}: {e}")
270
+ self._health_status[provider_name] = False
271
+ health_results[provider_name] = {
272
+ 'healthy': False,
273
+ 'message': f"Health check error: {e}",
274
+ 'data': {}
275
+ }
276
+
277
+ overall_healthy = healthy_count > 0
273
278
 
274
- # Get current metrics
275
- current_metrics = cache.get(metric_key, {
276
- 'total_requests': 0,
277
- 'successful_requests': 0,
278
- 'average_response_time': 0,
279
- 'last_updated': timezone.now().isoformat()
280
- })
279
+ return ServiceOperationResult(
280
+ success=overall_healthy,
281
+ message=f"{healthy_count}/{len(self._providers)} providers healthy",
282
+ data={
283
+ 'total_providers': len(self._providers),
284
+ 'healthy_providers': healthy_count,
285
+ 'unhealthy_providers': len(self._providers) - healthy_count,
286
+ 'provider_health': health_results,
287
+ 'available_providers': self.get_available_providers()
288
+ }
289
+ )
281
290
 
282
- # Update metrics
283
- total_requests = current_metrics['total_requests'] + 1
284
- successful_requests = current_metrics['successful_requests'] + (1 if success else 0)
291
+ except Exception as e:
292
+ logger.error(f"Health check failed: {e}")
293
+ return ServiceOperationResult(
294
+ success=False,
295
+ message=f"Health check error: {e}",
296
+ error_code="health_check_error"
297
+ )
298
+
299
+ def refresh_configurations(self) -> ServiceOperationResult:
300
+ """
301
+ Refresh provider configurations from config service.
302
+
303
+ Returns:
304
+ ServiceOperationResult: Refresh result
305
+ """
306
+ try:
307
+ logger.info("Refreshing provider configurations")
285
308
 
286
- # Calculate rolling average response time
287
- current_avg = current_metrics['average_response_time']
288
- new_avg = ((current_avg * current_metrics['total_requests']) + response_time_ms) / total_requests
309
+ # Clear current providers
310
+ self._providers.clear()
311
+ self._health_status.clear()
312
+ self._initialized = False
289
313
 
290
- updated_metrics = {
291
- 'total_requests': total_requests,
292
- 'successful_requests': successful_requests,
293
- 'success_rate': (successful_requests / total_requests) * 100,
294
- 'average_response_time': int(new_avg),
295
- 'last_response_time': response_time_ms,
296
- 'last_updated': timezone.now().isoformat()
314
+ # Refresh config service
315
+ self.config_service.refresh_configuration()
316
+
317
+ # Re-initialize providers
318
+ return self.initialize()
319
+
320
+ except Exception as e:
321
+ logger.error(f"Configuration refresh failed: {e}")
322
+ return ServiceOperationResult(
323
+ success=False,
324
+ message=f"Configuration refresh failed: {e}",
325
+ error_code="refresh_failed"
326
+ )
327
+
328
+ def get_registry_stats(self) -> ServiceOperationResult:
329
+ """
330
+ Get registry statistics.
331
+
332
+ Returns:
333
+ ServiceOperationResult: Registry statistics
334
+ """
335
+ try:
336
+ stats = {
337
+ 'initialized': self._initialized,
338
+ 'total_providers': len(self._providers),
339
+ 'healthy_providers': sum(1 for h in self._health_status.values() if h),
340
+ 'available_provider_classes': list(self._provider_classes.keys()),
341
+ 'configured_providers': list(self._providers.keys()),
342
+ 'health_status': dict(self._health_status)
297
343
  }
298
344
 
299
- # Store for 24 hours
300
- cache.set(metric_key, updated_metrics, timeout=86400)
345
+ return ServiceOperationResult(
346
+ success=True,
347
+ message="Registry statistics",
348
+ data=stats
349
+ )
301
350
 
302
- # Log performance issues
303
- if not success:
304
- logger.warning(f"Provider {provider_name} operation {operation} failed (response time: {response_time_ms}ms)")
305
- elif response_time_ms > 5000: # > 5 seconds
306
- logger.warning(f"Provider {provider_name} operation {operation} slow (response time: {response_time_ms}ms)")
307
-
308
351
  except Exception as e:
309
- logger.error(f"Error recording provider performance: {e}")
352
+ logger.error(f"Failed to get registry stats: {e}")
353
+ return ServiceOperationResult(
354
+ success=False,
355
+ message=f"Stats error: {e}",
356
+ error_code="stats_error"
357
+ )
310
358
 
311
- def get_provider_metrics(self, provider_name: str = None) -> Dict[str, Dict[str, Any]]:
359
+ def _create_provider(self, provider_name: str, config_data: Dict[str, Any]) -> Optional[BaseProvider]:
312
360
  """
313
- Get performance metrics for providers.
361
+ Create provider instance from configuration.
314
362
 
315
363
  Args:
316
- provider_name: Specific provider or None for all providers
364
+ provider_name: Provider name
365
+ config_data: Provider configuration data
317
366
 
318
367
  Returns:
319
- Dict of provider metrics
368
+ Optional[BaseProvider]: Provider instance or None
320
369
  """
321
- if provider_name:
322
- providers_to_check = [provider_name]
323
- else:
324
- providers_to_check = self.list_providers()
325
-
326
- metrics = {}
327
-
328
- for provider in providers_to_check:
329
- provider_metrics = {}
370
+ try:
371
+ # Get provider class
372
+ provider_class = self._provider_classes.get(provider_name)
373
+ if not provider_class:
374
+ logger.error(f"Unknown provider class: {provider_name}")
375
+ return None
330
376
 
331
- # Common operations to check
332
- operations = ['create_payment', 'check_status', 'process_webhook', 'get_currencies']
377
+ # Get config class
378
+ config_class = self._provider_configs.get(provider_name)
379
+ if not config_class:
380
+ logger.error(f"Unknown provider config class: {provider_name}")
381
+ return None
333
382
 
334
- for operation in operations:
335
- metric_key = f"provider_metrics:{provider}:{operation}"
336
- operation_metrics = cache.get(metric_key)
337
-
338
- if operation_metrics:
339
- provider_metrics[operation] = operation_metrics
383
+ # Create configuration
384
+ config = config_class(**config_data)
340
385
 
341
- if provider_metrics:
342
- metrics[provider] = provider_metrics
386
+ # Create provider
387
+ provider = provider_class(config)
388
+
389
+ logger.debug(f"Created provider: {provider}")
390
+ return provider
391
+
392
+ except Exception as e:
393
+ logger.error(f"Failed to create provider {provider_name}: {e}")
394
+ return None
395
+
396
+ def register_provider_class(
397
+ self,
398
+ provider_name: str,
399
+ provider_class: Type[BaseProvider],
400
+ config_class: Type[ProviderConfig]
401
+ ):
402
+ """
403
+ Register new provider class.
343
404
 
344
- return metrics
405
+ Args:
406
+ provider_name: Provider name
407
+ provider_class: Provider class
408
+ config_class: Provider config class
409
+ """
410
+ self._provider_classes[provider_name] = provider_class
411
+ self._provider_configs[provider_name] = config_class
412
+ logger.info(f"Registered provider class: {provider_name}")
345
413
 
346
- def reload_providers(self) -> None:
347
- """Reload all providers from configuration."""
348
- logger.info("Reloading providers from configuration")
349
- self._providers.clear()
350
- self._load_configurations()
351
-
414
+ def __len__(self) -> int:
415
+ """Get number of initialized providers."""
416
+ return len(self._providers)
417
+
418
+ def __contains__(self, provider_name: str) -> bool:
419
+ """Check if provider is initialized."""
420
+ return provider_name in self._providers
421
+
422
+ def __iter__(self):
423
+ """Iterate over provider names."""
424
+ return iter(self._providers.keys())
352
425
 
353
- # Global singleton instance
354
- _registry_instance = None
355
426
 
356
- def get_provider_registry() -> ProviderRegistry:
357
- """Get global provider registry instance."""
358
- global _registry_instance
359
- if _registry_instance is None:
360
- _registry_instance = ProviderRegistry()
361
- return _registry_instance
427
+ # Global registry instance
428
+ _global_registry: Optional[ProviderRegistry] = None
362
429
 
363
430
 
364
- def get_payment_provider(provider_name: str) -> Optional[PaymentProvider]:
431
+ def get_provider_registry() -> ProviderRegistry:
365
432
  """
366
- Get payment provider instance by name.
433
+ Get global provider registry instance.
367
434
 
368
- Args:
369
- provider_name: Name of provider (e.g. 'nowpayments', 'stripe')
370
-
371
435
  Returns:
372
- Provider instance or None if not found
436
+ ProviderRegistry: Global registry instance
373
437
  """
374
- registry = get_provider_registry()
375
- return registry.get_provider(provider_name)
438
+ global _global_registry
439
+ if _global_registry is None:
440
+ _global_registry = ProviderRegistry()
441
+ return _global_registry
376
442
 
377
443
 
378
- def get_available_providers() -> List[str]:
444
+ def initialize_providers() -> ServiceOperationResult:
379
445
  """
380
- Get list of available provider names.
446
+ Initialize global provider registry.
381
447
 
382
448
  Returns:
383
- List of provider names that are configured
449
+ ServiceOperationResult: Initialization result
384
450
  """
385
451
  registry = get_provider_registry()
386
- return registry.list_providers()
452
+ return registry.initialize()