django-cfg 1.2.31__py3-none-any.whl → 1.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,432 +0,0 @@
1
- """
2
- Provider Fallback Service.
3
-
4
- Handles automatic provider switching when providers become unavailable,
5
- ensuring payment system resilience and high availability.
6
- """
7
-
8
- from django_cfg.modules.django_logger import get_logger
9
- from typing import Optional, List, Dict, Any
10
- from dataclasses import dataclass
11
- from enum import Enum
12
-
13
- from django.core.cache import cache
14
- from django.utils import timezone
15
- from pydantic import BaseModel, Field
16
-
17
- from ..monitoring.provider_health import get_health_monitor, HealthStatus
18
- from ..providers.registry import ProviderRegistry
19
- from ...models.events import PaymentEvent
20
-
21
- logger = get_logger("fallback_service")
22
-
23
-
24
- class FallbackStrategy(Enum):
25
- """Provider fallback strategies."""
26
- ROUND_ROBIN = "round_robin"
27
- PRIORITY_BASED = "priority_based"
28
- HEALTH_BASED = "health_based"
29
- RANDOM = "random"
30
-
31
-
32
- class ProviderPriority(BaseModel):
33
- """Provider priority configuration."""
34
- provider_name: str = Field(..., description="Provider name")
35
- priority: int = Field(..., description="Priority (1=highest)")
36
- enabled: bool = Field(default=True, description="Is provider enabled for fallback")
37
- max_retry_attempts: int = Field(default=3, description="Max retry attempts before fallback")
38
-
39
-
40
- class FallbackResult(BaseModel):
41
- """Result of fallback provider selection."""
42
- success: bool = Field(..., description="Whether fallback was successful")
43
- original_provider: str = Field(..., description="Original provider that failed")
44
- fallback_provider: Optional[str] = Field(None, description="Selected fallback provider")
45
- reason: str = Field(..., description="Reason for fallback")
46
- retry_attempt: int = Field(default=0, description="Current retry attempt")
47
- metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
48
-
49
-
50
- class ProviderFallbackService:
51
- """
52
- Manages provider fallback logic for payment processing.
53
-
54
- Features:
55
- - Multiple fallback strategies
56
- - Provider priority management
57
- - Health-based switching
58
- - Retry logic with exponential backoff
59
- - Fallback event tracking
60
- """
61
-
62
- def __init__(self):
63
- """Initialize fallback service."""
64
- self.health_monitor = get_health_monitor()
65
- self.provider_registry = ProviderRegistry()
66
-
67
- # Default provider priorities (can be configured)
68
- self.provider_priorities = [
69
- ProviderPriority(provider_name="cryptapi", priority=1, enabled=True),
70
- ProviderPriority(provider_name="cryptomus", priority=2, enabled=True),
71
- ProviderPriority(provider_name="nowpayments", priority=3, enabled=True),
72
- ProviderPriority(provider_name="stripe", priority=4, enabled=True),
73
- ]
74
-
75
- self.default_strategy = FallbackStrategy.HEALTH_BASED
76
- self.max_fallback_attempts = 3
77
- self.fallback_cache_timeout = 600 # 10 minutes
78
-
79
- def get_fallback_provider(
80
- self,
81
- failed_provider: str,
82
- currency: str = None,
83
- strategy: FallbackStrategy = None,
84
- retry_attempt: int = 0
85
- ) -> FallbackResult:
86
- """
87
- Get fallback provider when primary provider fails.
88
-
89
- Args:
90
- failed_provider: Name of the provider that failed
91
- currency: Required currency (for provider compatibility)
92
- strategy: Fallback strategy to use
93
- retry_attempt: Current retry attempt number
94
-
95
- Returns:
96
- FallbackResult with fallback provider selection
97
- """
98
- if retry_attempt >= self.max_fallback_attempts:
99
- return FallbackResult(
100
- success=False,
101
- original_provider=failed_provider,
102
- reason=f"Max fallback attempts ({self.max_fallback_attempts}) exceeded",
103
- retry_attempt=retry_attempt
104
- )
105
-
106
- strategy = strategy or self.default_strategy
107
-
108
- try:
109
- # Get available providers based on strategy
110
- fallback_provider = self._select_fallback_provider(
111
- failed_provider=failed_provider,
112
- currency=currency,
113
- strategy=strategy
114
- )
115
-
116
- if not fallback_provider:
117
- return FallbackResult(
118
- success=False,
119
- original_provider=failed_provider,
120
- reason="No healthy fallback providers available",
121
- retry_attempt=retry_attempt
122
- )
123
-
124
- # Record fallback event
125
- self._record_fallback_event(
126
- failed_provider=failed_provider,
127
- fallback_provider=fallback_provider,
128
- strategy=strategy.value,
129
- retry_attempt=retry_attempt
130
- )
131
-
132
- # Cache fallback selection temporarily
133
- cache_key = f"fallback_{failed_provider}_{fallback_provider}"
134
- cache.set(cache_key, True, self.fallback_cache_timeout)
135
-
136
- logger.info(f"Fallback selected: {failed_provider} -> {fallback_provider} (attempt {retry_attempt + 1})")
137
-
138
- return FallbackResult(
139
- success=True,
140
- original_provider=failed_provider,
141
- fallback_provider=fallback_provider,
142
- reason=f"Fallback using {strategy.value} strategy",
143
- retry_attempt=retry_attempt,
144
- metadata={
145
- 'strategy': strategy.value,
146
- 'currency_filter': currency
147
- }
148
- )
149
-
150
- except Exception as e:
151
- logger.error(f"Error selecting fallback provider: {e}")
152
- return FallbackResult(
153
- success=False,
154
- original_provider=failed_provider,
155
- reason=f"Fallback selection error: {str(e)}",
156
- retry_attempt=retry_attempt
157
- )
158
-
159
- def _select_fallback_provider(
160
- self,
161
- failed_provider: str,
162
- currency: str = None,
163
- strategy: FallbackStrategy = FallbackStrategy.HEALTH_BASED
164
- ) -> Optional[str]:
165
- """
166
- Select fallback provider based on strategy.
167
-
168
- Args:
169
- failed_provider: Provider that failed
170
- currency: Required currency
171
- strategy: Fallback strategy
172
-
173
- Returns:
174
- Name of fallback provider or None
175
- """
176
- # Get all available providers
177
- available_providers = list(self.provider_registry.get_all_providers().keys())
178
-
179
- # Remove failed provider
180
- available_providers = [p for p in available_providers if p != failed_provider]
181
-
182
- if not available_providers:
183
- return None
184
-
185
- # Filter by enabled providers
186
- enabled_providers = [
187
- p for p in available_providers
188
- if self._is_provider_enabled(p)
189
- ]
190
-
191
- if not enabled_providers:
192
- return None
193
-
194
- # Filter by currency support if specified
195
- if currency:
196
- currency_compatible = []
197
- for provider_name in enabled_providers:
198
- provider = self.provider_registry.get_provider(provider_name)
199
- if provider and hasattr(provider, 'get_supported_currencies'):
200
- supported = provider.get_supported_currencies()
201
- if currency.upper() in [c.upper() for c in supported]:
202
- currency_compatible.append(provider_name)
203
- enabled_providers = currency_compatible
204
-
205
- if not enabled_providers:
206
- return None
207
-
208
- # Apply fallback strategy
209
- if strategy == FallbackStrategy.HEALTH_BASED:
210
- return self._select_by_health(enabled_providers)
211
- elif strategy == FallbackStrategy.PRIORITY_BASED:
212
- return self._select_by_priority(enabled_providers)
213
- elif strategy == FallbackStrategy.ROUND_ROBIN:
214
- return self._select_round_robin(enabled_providers)
215
- elif strategy == FallbackStrategy.RANDOM:
216
- import random
217
- return random.choice(enabled_providers)
218
- else:
219
- # Default to health-based
220
- return self._select_by_health(enabled_providers)
221
-
222
- def _select_by_health(self, providers: List[str]) -> Optional[str]:
223
- """Select provider based on health status and response time."""
224
- healthy_providers = self.health_monitor.get_healthy_providers()
225
-
226
- # Filter to only healthy providers from the available list
227
- candidates = [p for p in providers if p in healthy_providers]
228
-
229
- if not candidates:
230
- # No healthy providers, try degraded ones
231
- all_health = self.health_monitor.check_all_providers()
232
- degraded = [
233
- p.provider_name for p in all_health.providers
234
- if p.status == HealthStatus.DEGRADED and p.provider_name in providers
235
- ]
236
- candidates = degraded
237
-
238
- if not candidates:
239
- return None
240
-
241
- # Select provider with best response time among healthy ones
242
- if len(candidates) == 1:
243
- return candidates[0]
244
-
245
- # Get response times and select fastest
246
- best_provider = None
247
- best_response_time = float('inf')
248
-
249
- for provider_name in candidates:
250
- health_check = self.health_monitor.check_provider_health(provider_name)
251
- if health_check.response_time_ms < best_response_time:
252
- best_response_time = health_check.response_time_ms
253
- best_provider = provider_name
254
-
255
- return best_provider
256
-
257
- def _select_by_priority(self, providers: List[str]) -> Optional[str]:
258
- """Select provider based on configured priority."""
259
- # Sort providers by priority
260
- provider_priorities = {p.provider_name: p.priority for p in self.provider_priorities}
261
-
262
- # Filter and sort available providers by priority
263
- available_with_priority = [
264
- (provider, provider_priorities.get(provider, 999))
265
- for provider in providers
266
- if provider in provider_priorities
267
- ]
268
-
269
- if not available_with_priority:
270
- # If no priorities configured, return first available
271
- return providers[0] if providers else None
272
-
273
- # Sort by priority (lower number = higher priority)
274
- available_with_priority.sort(key=lambda x: x[1])
275
-
276
- return available_with_priority[0][0]
277
-
278
- def _select_round_robin(self, providers: List[str]) -> Optional[str]:
279
- """Select provider using round-robin algorithm."""
280
- if not providers:
281
- return None
282
-
283
- # Get current round-robin index from cache
284
- cache_key = "fallback_round_robin_index"
285
- current_index = cache.get(cache_key, 0)
286
-
287
- # Select provider at current index
288
- selected_provider = providers[current_index % len(providers)]
289
-
290
- # Update index for next round
291
- next_index = (current_index + 1) % len(providers)
292
- cache.set(cache_key, next_index, 86400) # Cache for 24 hours
293
-
294
- return selected_provider
295
-
296
- def _is_provider_enabled(self, provider_name: str) -> bool:
297
- """Check if provider is enabled for fallback."""
298
- for priority_config in self.provider_priorities:
299
- if priority_config.provider_name == provider_name:
300
- return priority_config.enabled
301
-
302
- # Default to enabled if not configured
303
- return True
304
-
305
- def _record_fallback_event(
306
- self,
307
- failed_provider: str,
308
- fallback_provider: str,
309
- strategy: str,
310
- retry_attempt: int
311
- ):
312
- """Record fallback event for audit trail."""
313
- try:
314
- PaymentEvent.objects.create(
315
- payment_id=f"fallback_{timezone.now().timestamp()}",
316
- event_type='provider_fallback',
317
- sequence_number=1,
318
- event_data={
319
- 'failed_provider': failed_provider,
320
- 'fallback_provider': fallback_provider,
321
- 'strategy': strategy,
322
- 'retry_attempt': retry_attempt,
323
- 'timestamp': timezone.now().isoformat()
324
- },
325
- processed_by='fallback_service',
326
- idempotency_key=f"fallback_{failed_provider}_{fallback_provider}_{timezone.now().timestamp()}"
327
- )
328
-
329
- except Exception as e:
330
- logger.error(f"Failed to record fallback event: {e}")
331
-
332
- def configure_provider_priority(self, provider_name: str, priority: int, enabled: bool = True):
333
- """
334
- Configure provider priority for fallback.
335
-
336
- Args:
337
- provider_name: Name of the provider
338
- priority: Priority level (1=highest)
339
- enabled: Whether provider is enabled for fallback
340
- """
341
- # Update existing priority or create new one
342
- for i, priority_config in enumerate(self.provider_priorities):
343
- if priority_config.provider_name == provider_name:
344
- self.provider_priorities[i] = ProviderPriority(
345
- provider_name=provider_name,
346
- priority=priority,
347
- enabled=enabled
348
- )
349
- return
350
-
351
- # Add new priority configuration
352
- self.provider_priorities.append(ProviderPriority(
353
- provider_name=provider_name,
354
- priority=priority,
355
- enabled=enabled
356
- ))
357
-
358
- # Re-sort by priority
359
- self.provider_priorities.sort(key=lambda x: x.priority)
360
-
361
- logger.info(f"Updated priority for {provider_name}: priority={priority}, enabled={enabled}")
362
-
363
- def get_fallback_statistics(self, days: int = 7) -> Dict[str, Any]:
364
- """
365
- Get fallback statistics for the specified period.
366
-
367
- Args:
368
- days: Number of days to analyze
369
-
370
- Returns:
371
- Dict with fallback statistics
372
- """
373
- try:
374
- since_date = timezone.now() - timezone.timedelta(days=days)
375
-
376
- fallback_events = PaymentEvent.objects.filter(
377
- event_type='provider_fallback',
378
- created_at__gte=since_date
379
- )
380
-
381
- stats = {
382
- 'total_fallbacks': fallback_events.count(),
383
- 'fallbacks_by_provider': {},
384
- 'fallbacks_by_strategy': {},
385
- 'most_failed_provider': None,
386
- 'most_used_fallback': None,
387
- 'period_days': days
388
- }
389
-
390
- # Analyze fallback patterns
391
- for event in fallback_events:
392
- data = event.event_data
393
- failed_provider = data.get('failed_provider')
394
- fallback_provider = data.get('fallback_provider')
395
- strategy = data.get('strategy')
396
-
397
- # Count by failed provider
398
- if failed_provider:
399
- stats['fallbacks_by_provider'][failed_provider] = \
400
- stats['fallbacks_by_provider'].get(failed_provider, 0) + 1
401
-
402
- # Count by strategy
403
- if strategy:
404
- stats['fallbacks_by_strategy'][strategy] = \
405
- stats['fallbacks_by_strategy'].get(strategy, 0) + 1
406
-
407
- # Find most problematic provider
408
- if stats['fallbacks_by_provider']:
409
- stats['most_failed_provider'] = max(
410
- stats['fallbacks_by_provider'].items(),
411
- key=lambda x: x[1]
412
- )[0]
413
-
414
- return stats
415
-
416
- except Exception as e:
417
- logger.error(f"Failed to get fallback statistics: {e}")
418
- return {
419
- 'total_fallbacks': 0,
420
- 'fallbacks_by_provider': {},
421
- 'fallbacks_by_strategy': {},
422
- 'error': str(e)
423
- }
424
-
425
-
426
- # Global fallback service instance
427
- fallback_service = ProviderFallbackService()
428
-
429
-
430
- def get_fallback_service() -> ProviderFallbackService:
431
- """Get global fallback service instance."""
432
- return fallback_service