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.
- 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 -10
- 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 +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- 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 +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- 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 +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- 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 +172 -148
- 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 -285
- 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 +346 -467
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- 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 +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- 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 +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- 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 +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- 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/config.py +1 -1
- django_cfg/core/config.py +40 -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 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
- 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 -146
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
- django_cfg/apps/payments/managers/__init__.py +0 -23
- 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 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- 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 -63
- 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 -461
- 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 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- 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/admin/payments/currency/change_list.html +0 -50
- 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 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- 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 -63
- 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 -122
- 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 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- 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 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- 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 -66
- django_cfg/core/integration.py +0 -160
- 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.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {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
|
2
|
+
Provider registry for the Universal Payment System v2.0.
|
3
3
|
|
4
|
-
|
4
|
+
Centralized management of payment providers with health monitoring and fallbacks.
|
5
5
|
"""
|
6
6
|
|
7
|
-
import
|
8
|
-
from typing import
|
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
|
-
"""
|
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
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
self.
|
37
|
-
self.
|
38
|
-
|
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
|
41
|
-
"""
|
71
|
+
def initialize(self) -> ServiceOperationResult:
|
72
|
+
"""
|
73
|
+
Initialize all configured providers.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
ServiceOperationResult: Initialization result
|
77
|
+
"""
|
42
78
|
try:
|
43
|
-
|
79
|
+
logger.info("Initializing provider registry")
|
44
80
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
118
|
-
priority_order = ['nowpayments', 'cryptapi', 'cryptomus', 'stripe']
|
114
|
+
self._initialized = True
|
119
115
|
|
120
|
-
|
116
|
+
# Perform initial health check
|
117
|
+
self.health_check_all()
|
121
118
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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"
|
131
|
-
|
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
|
-
|
142
|
+
def get_provider(self, provider_name: str) -> Optional[BaseProvider]:
|
134
143
|
"""
|
135
|
-
|
144
|
+
Get provider by name.
|
136
145
|
|
146
|
+
Args:
|
147
|
+
provider_name: Provider name
|
148
|
+
|
137
149
|
Returns:
|
138
|
-
|
150
|
+
Optional[BaseProvider]: Provider instance or None
|
139
151
|
"""
|
140
|
-
|
152
|
+
if not self._initialized:
|
153
|
+
logger.warning("Registry not initialized, initializing now")
|
154
|
+
self.initialize()
|
141
155
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
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
|
167
|
+
def get_available_providers(self) -> List[str]:
|
199
168
|
"""
|
200
|
-
Get list of healthy providers
|
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
|
172
|
+
List[str]: List of available provider names
|
207
173
|
"""
|
208
|
-
|
174
|
+
if not self._initialized:
|
175
|
+
self.initialize()
|
209
176
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
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
|
205
|
+
def get_provider_for_currency(self, currency_code: str) -> Optional[BaseProvider]:
|
223
206
|
"""
|
224
|
-
Get provider
|
207
|
+
Get best provider for specific currency.
|
225
208
|
|
226
209
|
Args:
|
227
|
-
|
228
|
-
operation: Operation type for provider selection
|
210
|
+
currency_code: Currency code
|
229
211
|
|
230
212
|
Returns:
|
231
|
-
|
213
|
+
Optional[BaseProvider]: Best provider for currency or None
|
232
214
|
"""
|
233
|
-
|
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
|
-
#
|
248
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
257
|
-
return
|
228
|
+
# Return first supporting provider (could be enhanced with more logic)
|
229
|
+
return supporting_providers[0]
|
258
230
|
|
259
|
-
def
|
260
|
-
response_time_ms: int, success: bool) -> None:
|
231
|
+
def health_check_all(self) -> ServiceOperationResult:
|
261
232
|
"""
|
262
|
-
|
233
|
+
Perform health check on all providers.
|
263
234
|
|
264
|
-
|
265
|
-
|
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
|
-
|
272
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
283
|
-
|
284
|
-
|
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
|
-
#
|
287
|
-
|
288
|
-
|
309
|
+
# Clear current providers
|
310
|
+
self._providers.clear()
|
311
|
+
self._health_status.clear()
|
312
|
+
self._initialized = False
|
289
313
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
300
|
-
|
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"
|
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
|
359
|
+
def _create_provider(self, provider_name: str, config_data: Dict[str, Any]) -> Optional[BaseProvider]:
|
312
360
|
"""
|
313
|
-
|
361
|
+
Create provider instance from configuration.
|
314
362
|
|
315
363
|
Args:
|
316
|
-
provider_name:
|
364
|
+
provider_name: Provider name
|
365
|
+
config_data: Provider configuration data
|
317
366
|
|
318
367
|
Returns:
|
319
|
-
|
368
|
+
Optional[BaseProvider]: Provider instance or None
|
320
369
|
"""
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
#
|
332
|
-
|
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
|
-
|
335
|
-
|
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
|
-
|
342
|
-
|
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
|
-
|
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
|
347
|
-
"""
|
348
|
-
|
349
|
-
|
350
|
-
|
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
|
-
|
357
|
-
|
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
|
431
|
+
def get_provider_registry() -> ProviderRegistry:
|
365
432
|
"""
|
366
|
-
Get
|
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
|
-
|
436
|
+
ProviderRegistry: Global registry instance
|
373
437
|
"""
|
374
|
-
|
375
|
-
|
438
|
+
global _global_registry
|
439
|
+
if _global_registry is None:
|
440
|
+
_global_registry = ProviderRegistry()
|
441
|
+
return _global_registry
|
376
442
|
|
377
443
|
|
378
|
-
def
|
444
|
+
def initialize_providers() -> ServiceOperationResult:
|
379
445
|
"""
|
380
|
-
|
446
|
+
Initialize global provider registry.
|
381
447
|
|
382
448
|
Returns:
|
383
|
-
|
449
|
+
ServiceOperationResult: Initialization result
|
384
450
|
"""
|
385
451
|
registry = get_provider_registry()
|
386
|
-
return registry.
|
452
|
+
return registry.initialize()
|