django-cfg 1.2.29__py3-none-any.whl → 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -9
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +600 -108
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +470 -64
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
- django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +16 -20
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +207 -67
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -284
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +344 -468
- django_cfg/apps/payments/services/core/subscription_service.py +425 -484
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +232 -71
- django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
- django_cfg/apps/payments/services/providers/registry.py +429 -80
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +211 -130
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +129 -98
- django_cfg/apps/payments/signals/subscription_signals.py +195 -143
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +46 -47
- django_cfg/apps/payments/urls_admin.py +49 -0
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +4 -4
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +75 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -498
- django_cfg/modules/django_currency/__init__.py +16 -11
- django_cfg/modules/django_currency/clients/__init__.py +4 -4
- django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
- django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
- django_cfg/modules/django_currency/core/__init__.py +1 -7
- django_cfg/modules/django_currency/core/converter.py +18 -23
- django_cfg/modules/django_currency/core/models.py +122 -11
- django_cfg/modules/django_currency/database/__init__.py +4 -4
- django_cfg/modules/django_currency/database/database_loader.py +190 -309
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +65 -12
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/admin/components/action_grid.html +9 -9
- django_cfg/templates/admin/components/metric_card.html +5 -5
- django_cfg/templates/admin/components/status_badge.html +2 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
- django_cfg/templates/admin/snippets/components/system_health.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -178
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
- django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
- django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
- django_cfg/apps/payments/managers/__init__.py +0 -22
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -83
- django_cfg/apps/payments/managers/payment_manager.py +0 -44
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -56
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -55
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -297
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -637
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/urls_templates.py +0 -52
- django_cfg/apps/payments/utils/__init__.py +0 -45
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -245
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -62
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -111
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -312
- django_cfg/apps/payments/views/templates/base.py +0 -204
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -164
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -240
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -65
- django_cfg/core/integration.py +0 -160
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.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
|
-
import logging
|
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 = logging.getLogger(__name__)
|
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
|