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,372 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Provider Health Monitoring System.
|
3
|
-
|
4
|
-
Monitors the health of all payment providers and provides
|
5
|
-
fallback mechanisms when providers are unavailable.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from django_cfg.modules.django_logger import get_logger
|
9
|
-
import time
|
10
|
-
import asyncio
|
11
|
-
import requests
|
12
|
-
from typing import Dict, List, Optional, Any
|
13
|
-
from datetime import datetime, timedelta
|
14
|
-
from dataclasses import dataclass
|
15
|
-
from enum import Enum
|
16
|
-
|
17
|
-
from django.utils import timezone
|
18
|
-
from django.core.cache import cache
|
19
|
-
from pydantic import BaseModel, Field
|
20
|
-
|
21
|
-
from ..providers.registry import ProviderRegistry
|
22
|
-
from ...models.events import PaymentEvent
|
23
|
-
from .api_schemas import parse_provider_response
|
24
|
-
|
25
|
-
logger = get_logger("provider_health")
|
26
|
-
|
27
|
-
|
28
|
-
class HealthStatus(Enum):
|
29
|
-
"""Provider health status levels."""
|
30
|
-
HEALTHY = "healthy"
|
31
|
-
DEGRADED = "degraded"
|
32
|
-
UNHEALTHY = "unhealthy"
|
33
|
-
UNKNOWN = "unknown"
|
34
|
-
|
35
|
-
|
36
|
-
class ProviderHealthCheck(BaseModel):
|
37
|
-
"""Provider health check result."""
|
38
|
-
provider_name: str = Field(..., description="Provider name")
|
39
|
-
status: HealthStatus = Field(..., description="Health status")
|
40
|
-
response_time_ms: float = Field(..., description="Response time in milliseconds")
|
41
|
-
status_code: Optional[int] = Field(None, description="HTTP status code")
|
42
|
-
error_message: Optional[str] = Field(None, description="Error message if unhealthy")
|
43
|
-
checked_at: datetime = Field(default_factory=timezone.now, description="Check timestamp")
|
44
|
-
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
45
|
-
|
46
|
-
|
47
|
-
class ProviderHealthSummary(BaseModel):
|
48
|
-
"""Summary of all provider health statuses."""
|
49
|
-
total_providers: int = Field(..., description="Total number of providers")
|
50
|
-
healthy_count: int = Field(..., description="Number of healthy providers")
|
51
|
-
degraded_count: int = Field(..., description="Number of degraded providers")
|
52
|
-
unhealthy_count: int = Field(..., description="Number of unhealthy providers")
|
53
|
-
providers: List[ProviderHealthCheck] = Field(..., description="Individual provider health checks")
|
54
|
-
last_updated: datetime = Field(default_factory=timezone.now, description="Last update timestamp")
|
55
|
-
|
56
|
-
|
57
|
-
class ProviderHealthMonitor:
|
58
|
-
"""
|
59
|
-
Monitor the health of all payment providers.
|
60
|
-
|
61
|
-
Features:
|
62
|
-
- Real-time health checks
|
63
|
-
- Provider availability tracking
|
64
|
-
- Automatic fallback recommendations
|
65
|
-
- Health history and trends
|
66
|
-
- Alert system integration
|
67
|
-
"""
|
68
|
-
|
69
|
-
def __init__(self):
|
70
|
-
"""Initialize health monitor."""
|
71
|
-
self.provider_registry = ProviderRegistry()
|
72
|
-
self.cache_timeout = 300 # 5 minutes
|
73
|
-
self.health_check_timeout = 10 # 10 seconds
|
74
|
-
|
75
|
-
# Health check endpoints for each provider
|
76
|
-
self.health_endpoints = {
|
77
|
-
'cryptapi': 'https://api.cryptapi.io/btc/info/',
|
78
|
-
'cryptomus': 'https://api.cryptomus.com', # Base URL check (returns 204 No Content = healthy)
|
79
|
-
'nowpayments': 'https://api.nowpayments.io/v1/status',
|
80
|
-
'stripe': 'https://api.stripe.com/v1/account' # Will return auth error = healthy
|
81
|
-
}
|
82
|
-
|
83
|
-
def check_all_providers(self) -> ProviderHealthSummary:
|
84
|
-
"""
|
85
|
-
Check health of all registered providers.
|
86
|
-
|
87
|
-
Returns:
|
88
|
-
ProviderHealthSummary with all provider statuses
|
89
|
-
"""
|
90
|
-
providers = self.provider_registry.get_all_providers()
|
91
|
-
health_checks = []
|
92
|
-
|
93
|
-
for provider_name, provider_instance in providers.items():
|
94
|
-
try:
|
95
|
-
health_check = self.check_provider_health(provider_name)
|
96
|
-
health_checks.append(health_check)
|
97
|
-
except Exception as e:
|
98
|
-
logger.error(f"Failed to check health for {provider_name}: {e}")
|
99
|
-
health_checks.append(ProviderHealthCheck(
|
100
|
-
provider_name=provider_name,
|
101
|
-
status=HealthStatus.UNKNOWN,
|
102
|
-
response_time_ms=0.0,
|
103
|
-
error_message=str(e)
|
104
|
-
))
|
105
|
-
|
106
|
-
# Calculate summary
|
107
|
-
healthy_count = sum(1 for check in health_checks if check.status == HealthStatus.HEALTHY)
|
108
|
-
degraded_count = sum(1 for check in health_checks if check.status == HealthStatus.DEGRADED)
|
109
|
-
unhealthy_count = sum(1 for check in health_checks if check.status == HealthStatus.UNHEALTHY)
|
110
|
-
|
111
|
-
summary = ProviderHealthSummary(
|
112
|
-
total_providers=len(health_checks),
|
113
|
-
healthy_count=healthy_count,
|
114
|
-
degraded_count=degraded_count,
|
115
|
-
unhealthy_count=unhealthy_count,
|
116
|
-
providers=health_checks
|
117
|
-
)
|
118
|
-
|
119
|
-
# Cache summary
|
120
|
-
cache.set('provider_health_summary', summary.dict(), self.cache_timeout)
|
121
|
-
|
122
|
-
# Log health summary
|
123
|
-
logger.info(f"Provider health check completed: {healthy_count}/{len(health_checks)} healthy")
|
124
|
-
|
125
|
-
return summary
|
126
|
-
|
127
|
-
def check_provider_health(self, provider_name: str) -> ProviderHealthCheck:
|
128
|
-
"""
|
129
|
-
Check health of a specific provider.
|
130
|
-
|
131
|
-
Args:
|
132
|
-
provider_name: Name of the provider to check
|
133
|
-
|
134
|
-
Returns:
|
135
|
-
ProviderHealthCheck with health status
|
136
|
-
"""
|
137
|
-
# Check cache first
|
138
|
-
cache_key = f'provider_health_{provider_name}'
|
139
|
-
cached_result = cache.get(cache_key)
|
140
|
-
if cached_result:
|
141
|
-
return ProviderHealthCheck(**cached_result)
|
142
|
-
|
143
|
-
start_time = time.time()
|
144
|
-
health_check = None
|
145
|
-
|
146
|
-
try:
|
147
|
-
# Get health endpoint for provider
|
148
|
-
endpoint = self.health_endpoints.get(provider_name)
|
149
|
-
if not endpoint:
|
150
|
-
raise ValueError(f"No health endpoint configured for {provider_name}")
|
151
|
-
|
152
|
-
# Make health check request
|
153
|
-
response = requests.get(
|
154
|
-
endpoint,
|
155
|
-
timeout=self.health_check_timeout,
|
156
|
-
headers={'User-Agent': 'DjangoCFG-PaymentMonitor/1.0'}
|
157
|
-
)
|
158
|
-
|
159
|
-
response_time = (time.time() - start_time) * 1000
|
160
|
-
|
161
|
-
# Parse response using Pydantic schemas
|
162
|
-
response_body = response.text if response.text else ""
|
163
|
-
parsed_health = parse_provider_response(
|
164
|
-
provider_name=provider_name,
|
165
|
-
status_code=response.status_code,
|
166
|
-
response_body=response_body,
|
167
|
-
response_time_ms=response_time
|
168
|
-
)
|
169
|
-
|
170
|
-
# Convert to our HealthStatus enum
|
171
|
-
if parsed_health.is_healthy:
|
172
|
-
status = HealthStatus.HEALTHY
|
173
|
-
elif 400 <= response.status_code < 500:
|
174
|
-
status = HealthStatus.DEGRADED
|
175
|
-
else:
|
176
|
-
status = HealthStatus.UNHEALTHY
|
177
|
-
|
178
|
-
health_check = ProviderHealthCheck(
|
179
|
-
provider_name=provider_name,
|
180
|
-
status=status,
|
181
|
-
response_time_ms=round(response_time, 2),
|
182
|
-
status_code=response.status_code,
|
183
|
-
error_message=parsed_health.error_message,
|
184
|
-
metadata={
|
185
|
-
'endpoint': endpoint,
|
186
|
-
'response_size': len(response.content) if response.content else 0,
|
187
|
-
'parsed_response': parsed_health.parsed_response,
|
188
|
-
'pydantic_validated': True
|
189
|
-
}
|
190
|
-
)
|
191
|
-
|
192
|
-
except requests.exceptions.Timeout:
|
193
|
-
response_time = (time.time() - start_time) * 1000
|
194
|
-
health_check = ProviderHealthCheck(
|
195
|
-
provider_name=provider_name,
|
196
|
-
status=HealthStatus.UNHEALTHY,
|
197
|
-
response_time_ms=round(response_time, 2),
|
198
|
-
error_message="Request timeout"
|
199
|
-
)
|
200
|
-
|
201
|
-
except requests.exceptions.ConnectionError:
|
202
|
-
response_time = (time.time() - start_time) * 1000
|
203
|
-
health_check = ProviderHealthCheck(
|
204
|
-
provider_name=provider_name,
|
205
|
-
status=HealthStatus.UNHEALTHY,
|
206
|
-
response_time_ms=round(response_time, 2),
|
207
|
-
error_message="Connection error"
|
208
|
-
)
|
209
|
-
|
210
|
-
except Exception as e:
|
211
|
-
response_time = (time.time() - start_time) * 1000
|
212
|
-
health_check = ProviderHealthCheck(
|
213
|
-
provider_name=provider_name,
|
214
|
-
status=HealthStatus.UNKNOWN,
|
215
|
-
response_time_ms=round(response_time, 2),
|
216
|
-
error_message=str(e)
|
217
|
-
)
|
218
|
-
|
219
|
-
# Cache result
|
220
|
-
cache.set(cache_key, health_check.dict(), self.cache_timeout // 2) # Shorter cache for individual checks
|
221
|
-
|
222
|
-
# Log health check
|
223
|
-
logger.info(f"Health check for {provider_name}: {health_check.status.value} ({health_check.response_time_ms}ms)")
|
224
|
-
|
225
|
-
return health_check
|
226
|
-
|
227
|
-
def get_healthy_providers(self) -> List[str]:
|
228
|
-
"""
|
229
|
-
Get list of currently healthy provider names.
|
230
|
-
|
231
|
-
Returns:
|
232
|
-
List of healthy provider names
|
233
|
-
"""
|
234
|
-
summary = self.check_all_providers()
|
235
|
-
return [
|
236
|
-
provider.provider_name
|
237
|
-
for provider in summary.providers
|
238
|
-
if provider.status == HealthStatus.HEALTHY
|
239
|
-
]
|
240
|
-
|
241
|
-
def get_fallback_provider(self, preferred_provider: str) -> Optional[str]:
|
242
|
-
"""
|
243
|
-
Get fallback provider when preferred provider is unhealthy.
|
244
|
-
|
245
|
-
Args:
|
246
|
-
preferred_provider: Name of preferred provider
|
247
|
-
|
248
|
-
Returns:
|
249
|
-
Name of healthy fallback provider or None
|
250
|
-
"""
|
251
|
-
healthy_providers = self.get_healthy_providers()
|
252
|
-
|
253
|
-
# Remove preferred provider from list
|
254
|
-
fallback_providers = [p for p in healthy_providers if p != preferred_provider]
|
255
|
-
|
256
|
-
if not fallback_providers:
|
257
|
-
logger.warning(f"No healthy fallback providers available for {preferred_provider}")
|
258
|
-
return None
|
259
|
-
|
260
|
-
# Return first healthy provider as fallback
|
261
|
-
fallback = fallback_providers[0]
|
262
|
-
logger.info(f"Fallback provider for {preferred_provider}: {fallback}")
|
263
|
-
|
264
|
-
return fallback
|
265
|
-
|
266
|
-
def record_provider_incident(self, provider_name: str, incident_type: str, details: Dict[str, Any]):
|
267
|
-
"""
|
268
|
-
Record provider incident for tracking.
|
269
|
-
|
270
|
-
Args:
|
271
|
-
provider_name: Name of the provider
|
272
|
-
incident_type: Type of incident (outage, degradation, etc.)
|
273
|
-
details: Incident details
|
274
|
-
"""
|
275
|
-
try:
|
276
|
-
PaymentEvent.objects.create(
|
277
|
-
payment_id=f"health_monitor_{timezone.now().timestamp()}",
|
278
|
-
event_type='provider_incident',
|
279
|
-
sequence_number=1,
|
280
|
-
event_data={
|
281
|
-
'provider_name': provider_name,
|
282
|
-
'incident_type': incident_type,
|
283
|
-
'details': details,
|
284
|
-
'timestamp': timezone.now().isoformat()
|
285
|
-
},
|
286
|
-
processed_by='health_monitor',
|
287
|
-
idempotency_key=f"incident_{provider_name}_{timezone.now().timestamp()}"
|
288
|
-
)
|
289
|
-
|
290
|
-
logger.warning(f"Provider incident recorded: {provider_name} - {incident_type}")
|
291
|
-
|
292
|
-
except Exception as e:
|
293
|
-
logger.error(f"Failed to record provider incident: {e}")
|
294
|
-
|
295
|
-
def get_provider_uptime(self, provider_name: str, days: int = 7) -> float:
|
296
|
-
"""
|
297
|
-
Calculate provider uptime percentage over specified period.
|
298
|
-
|
299
|
-
Args:
|
300
|
-
provider_name: Name of the provider
|
301
|
-
days: Number of days to calculate uptime for
|
302
|
-
|
303
|
-
Returns:
|
304
|
-
Uptime percentage (0.0 to 100.0)
|
305
|
-
"""
|
306
|
-
try:
|
307
|
-
# Get provider incidents from last N days
|
308
|
-
since_date = timezone.now() - timedelta(days=days)
|
309
|
-
|
310
|
-
incidents = PaymentEvent.objects.filter(
|
311
|
-
event_type='provider_incident',
|
312
|
-
event_data__provider_name=provider_name,
|
313
|
-
created_at__gte=since_date
|
314
|
-
).count()
|
315
|
-
|
316
|
-
# Simple uptime calculation (this could be more sophisticated)
|
317
|
-
total_checks = days * 24 * 12 # Assume checks every 5 minutes
|
318
|
-
uptime_percentage = max(0.0, ((total_checks - incidents) / total_checks) * 100.0)
|
319
|
-
|
320
|
-
return round(uptime_percentage, 2)
|
321
|
-
|
322
|
-
except Exception as e:
|
323
|
-
logger.error(f"Failed to calculate uptime for {provider_name}: {e}")
|
324
|
-
return 0.0
|
325
|
-
|
326
|
-
def generate_health_report(self) -> Dict[str, Any]:
|
327
|
-
"""
|
328
|
-
Generate comprehensive health report.
|
329
|
-
|
330
|
-
Returns:
|
331
|
-
Dict with health report data
|
332
|
-
"""
|
333
|
-
summary = self.check_all_providers()
|
334
|
-
|
335
|
-
report = {
|
336
|
-
'summary': summary.dict(),
|
337
|
-
'uptime_stats': {},
|
338
|
-
'recommendations': [],
|
339
|
-
'generated_at': timezone.now().isoformat()
|
340
|
-
}
|
341
|
-
|
342
|
-
# Calculate uptime for each provider
|
343
|
-
for provider in summary.providers:
|
344
|
-
uptime = self.get_provider_uptime(provider.provider_name)
|
345
|
-
report['uptime_stats'][provider.provider_name] = uptime
|
346
|
-
|
347
|
-
# Generate recommendations
|
348
|
-
if summary.unhealthy_count > 0:
|
349
|
-
unhealthy_providers = [p.provider_name for p in summary.providers if p.status == HealthStatus.UNHEALTHY]
|
350
|
-
report['recommendations'].append({
|
351
|
-
'type': 'critical',
|
352
|
-
'message': f"Unhealthy providers detected: {', '.join(unhealthy_providers)}",
|
353
|
-
'action': 'Check provider API status and credentials'
|
354
|
-
})
|
355
|
-
|
356
|
-
if summary.healthy_count < 2:
|
357
|
-
report['recommendations'].append({
|
358
|
-
'type': 'warning',
|
359
|
-
'message': 'Low number of healthy providers',
|
360
|
-
'action': 'Consider adding additional payment provider integrations'
|
361
|
-
})
|
362
|
-
|
363
|
-
return report
|
364
|
-
|
365
|
-
|
366
|
-
# Global health monitor instance
|
367
|
-
health_monitor = ProviderHealthMonitor()
|
368
|
-
|
369
|
-
|
370
|
-
def get_health_monitor() -> ProviderHealthMonitor:
|
371
|
-
"""Get global health monitor instance."""
|
372
|
-
return health_monitor
|
@@ -1,8 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
4
|
-
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo
|
5
|
-
dGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy
|
6
|
-
Bsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE
|
7
|
-
ox7pp208zTvown577wIDAQAB
|
8
|
-
-----END PUBLIC KEY-----"""
|
@@ -1,192 +0,0 @@
|
|
1
|
-
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
2
|
-
from typing import Optional, List, Dict, Any
|
3
|
-
from decimal import Decimal
|
4
|
-
|
5
|
-
from ...internal_types import ProviderConfig
|
6
|
-
from .config import PUBLIC_KEY
|
7
|
-
|
8
|
-
class CryptAPIConfig(ProviderConfig):
|
9
|
-
"""CryptAPI provider configuration with Pydantic v2."""
|
10
|
-
|
11
|
-
own_address: str = Field(..., description="Your cryptocurrency address")
|
12
|
-
callback_url: Optional[str] = Field(default=None, description="Webhook callback URL")
|
13
|
-
convert_payments: bool = Field(default=True, description="Auto-convert payments")
|
14
|
-
multi_token: bool = Field(default=True, description="Support multi-token payments")
|
15
|
-
priority: str = Field(default='default', description="Transaction priority")
|
16
|
-
verify_signatures: bool = Field(default=True, description="Enable webhook signature verification")
|
17
|
-
|
18
|
-
# CryptAPI's official public key for signature verification
|
19
|
-
public_key: str = Field(
|
20
|
-
default=PUBLIC_KEY,
|
21
|
-
description="CryptAPI RSA public key for signature verification"
|
22
|
-
)
|
23
|
-
|
24
|
-
|
25
|
-
class CryptAPICurrency(BaseModel):
|
26
|
-
"""CryptAPI specific currency model."""
|
27
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
28
|
-
|
29
|
-
currency_code: str = Field(..., description="Currency symbol (e.g., BTC, ETH)")
|
30
|
-
name: str = Field(..., description="Full currency name")
|
31
|
-
minimum_transaction: Optional[Decimal] = Field(None, description="Minimum transaction amount")
|
32
|
-
maximum_transaction: Optional[Decimal] = Field(None, description="Maximum transaction amount")
|
33
|
-
fee_percent: Optional[Decimal] = Field(None, description="Fee percentage")
|
34
|
-
logo: Optional[str] = Field(None, description="Currency logo URL")
|
35
|
-
|
36
|
-
|
37
|
-
class CryptAPINetwork(BaseModel):
|
38
|
-
"""CryptAPI specific network model."""
|
39
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
40
|
-
|
41
|
-
currency: str = Field(..., description="Currency code this network belongs to")
|
42
|
-
network: str = Field(..., description="Network code (e.g., mainnet, testnet)")
|
43
|
-
name: str = Field(..., description="Network display name")
|
44
|
-
confirmations: int = Field(1, description="Required confirmations")
|
45
|
-
fee: Optional[Decimal] = Field(None, description="Network fee")
|
46
|
-
|
47
|
-
|
48
|
-
class CryptAPIPaymentRequest(BaseModel):
|
49
|
-
"""CryptAPI payment creation request."""
|
50
|
-
ticker: str = Field(..., description="Currency ticker")
|
51
|
-
callback: str = Field(..., description="Callback URL")
|
52
|
-
address: Optional[str] = Field(None, description="Destination address")
|
53
|
-
pending: bool = Field(False, description="Accept pending transactions")
|
54
|
-
confirmations: int = Field(1, description="Required confirmations")
|
55
|
-
email: Optional[str] = Field(None, description="Email for notifications")
|
56
|
-
post: int = Field(0, description="POST data format")
|
57
|
-
json: int = Field(1, description="JSON response format")
|
58
|
-
priority: Optional[str] = Field(None, description="Priority level")
|
59
|
-
multi_token: bool = Field(False, description="Multi-token support")
|
60
|
-
convert: int = Field(1, description="Convert amounts")
|
61
|
-
|
62
|
-
|
63
|
-
class CryptAPIPaymentResponse(BaseModel):
|
64
|
-
"""CryptAPI payment creation response."""
|
65
|
-
address_in: str = Field(..., description="Payment address")
|
66
|
-
address_out: Optional[str] = Field(None, description="Destination address")
|
67
|
-
callback_url: str = Field(..., description="Callback URL")
|
68
|
-
priority: Optional[str] = Field(None, description="Priority level")
|
69
|
-
minimum: Optional[Decimal] = Field(None, description="Minimum amount")
|
70
|
-
|
71
|
-
|
72
|
-
class CryptAPICallback(BaseModel):
|
73
|
-
"""CryptAPI webhook callback data according to official documentation."""
|
74
|
-
model_config = ConfigDict(validate_assignment=True, extra="allow") # Allow extra fields for custom params
|
75
|
-
|
76
|
-
# Required fields from documentation
|
77
|
-
uuid: Optional[str] = Field(None, description="Unique identifier for each payment transaction")
|
78
|
-
address_in: str = Field(..., description="CryptAPI-generated payment address")
|
79
|
-
address_out: str = Field(..., description="Your destination address(es)")
|
80
|
-
txid_in: str = Field(..., description="Transaction hash of customer's payment")
|
81
|
-
coin: str = Field(..., description="Cryptocurrency ticker")
|
82
|
-
price: Optional[Decimal] = Field(None, description="Cryptocurrency price in USD")
|
83
|
-
pending: Optional[int] = Field(0, description="1=pending webhook, 0=confirmed webhook")
|
84
|
-
|
85
|
-
# Confirmed webhook only fields
|
86
|
-
txid_out: Optional[str] = Field(None, description="CryptAPI's forwarding transaction hash")
|
87
|
-
confirmations: Optional[int] = Field(None, description="Number of blockchain confirmations")
|
88
|
-
value_coin: Optional[Decimal] = Field(None, description="Payment amount before fees")
|
89
|
-
value_forwarded_coin: Optional[Decimal] = Field(None, description="Amount forwarded after fees")
|
90
|
-
fee_coin: Optional[Decimal] = Field(None, description="CryptAPI service fee")
|
91
|
-
|
92
|
-
# Optional conversion fields (when convert=1)
|
93
|
-
value_coin_convert: Optional[str] = Field(None, description="JSON FIAT conversions of value_coin")
|
94
|
-
value_forwarded_coin_convert: Optional[str] = Field(None, description="JSON FIAT conversions of value_forwarded_coin")
|
95
|
-
|
96
|
-
@field_validator('pending')
|
97
|
-
@classmethod
|
98
|
-
def validate_pending(cls, v):
|
99
|
-
"""Validate pending field is 0 or 1."""
|
100
|
-
if v not in [0, 1]:
|
101
|
-
raise ValueError("pending must be 0 (confirmed) or 1 (pending)")
|
102
|
-
return v
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
class CryptAPIInfoResponse(BaseModel):
|
107
|
-
"""CryptAPI info endpoint response."""
|
108
|
-
ticker: str = Field(..., description="Currency ticker")
|
109
|
-
minimum_transaction: Decimal = Field(..., description="Minimum transaction amount")
|
110
|
-
maximum_transaction: Optional[Decimal] = Field(None, description="Maximum transaction amount")
|
111
|
-
fee_percent: Decimal = Field(..., description="Fee percentage")
|
112
|
-
network_fee: Decimal = Field(..., description="Network fee")
|
113
|
-
prices: Dict[str, Decimal] = Field(..., description="Price conversions")
|
114
|
-
|
115
|
-
|
116
|
-
class CryptAPIEstimateFeeResponse(BaseModel):
|
117
|
-
"""CryptAPI fee estimation response."""
|
118
|
-
estimated_cost: Decimal = Field(..., description="Estimated cost")
|
119
|
-
estimated_cost_currency: Dict[str, Decimal] = Field(..., description="Cost in different currencies")
|
120
|
-
|
121
|
-
|
122
|
-
class CryptAPIConvertResponse(BaseModel):
|
123
|
-
"""CryptAPI currency conversion response."""
|
124
|
-
value_coin: Decimal = Field(..., description="Value in cryptocurrency")
|
125
|
-
exchange_rate: Decimal = Field(..., description="Exchange rate used")
|
126
|
-
|
127
|
-
|
128
|
-
class CryptAPIQRCodeResponse(BaseModel):
|
129
|
-
"""CryptAPI QR code response."""
|
130
|
-
qr_code: str = Field(..., description="Base64 encoded QR code image")
|
131
|
-
payment_uri: str = Field(..., description="Payment URI for QR code")
|
132
|
-
|
133
|
-
|
134
|
-
class CryptAPILogsResponse(BaseModel):
|
135
|
-
"""CryptAPI logs response."""
|
136
|
-
callbacks: List[Dict[str, Any]] = Field(default_factory=list, description="Callback logs")
|
137
|
-
payments: List[Dict[str, Any]] = Field(default_factory=list, description="Payment logs")
|
138
|
-
|
139
|
-
|
140
|
-
class CryptAPISupportedCoinsResponse(BaseModel):
|
141
|
-
"""CryptAPI supported coins response."""
|
142
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
143
|
-
|
144
|
-
currencies: List[CryptAPICurrency] = Field(..., description="List of supported currencies")
|
145
|
-
|
146
|
-
|
147
|
-
# =============================================================================
|
148
|
-
# MONITORING & HEALTH CHECK MODELS
|
149
|
-
# =============================================================================
|
150
|
-
|
151
|
-
class CryptAPIInfoResponse(BaseModel):
|
152
|
-
"""CryptAPI /btc/info/ response schema for health checks."""
|
153
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
154
|
-
|
155
|
-
coin: str = Field(..., description="Cryptocurrency name")
|
156
|
-
logo: str = Field(..., description="Logo URL")
|
157
|
-
ticker: str = Field(..., description="Currency ticker")
|
158
|
-
minimum_transaction: int = Field(..., description="Minimum transaction in satoshis")
|
159
|
-
minimum_transaction_coin: str = Field(..., description="Minimum transaction in coin units")
|
160
|
-
minimum_fee: int = Field(..., description="Minimum fee in satoshis")
|
161
|
-
minimum_fee_coin: str = Field(..., description="Minimum fee in coin units")
|
162
|
-
fee_percent: str = Field(..., description="Fee percentage")
|
163
|
-
network_fee_estimation: str = Field(..., description="Network fee estimation")
|
164
|
-
status: str = Field(..., description="API status")
|
165
|
-
prices: Dict[str, str] = Field(..., description="Prices in various fiat currencies")
|
166
|
-
prices_updated: str = Field(..., description="Prices last updated timestamp")
|
167
|
-
|
168
|
-
@field_validator('status')
|
169
|
-
@classmethod
|
170
|
-
def validate_status(cls, v):
|
171
|
-
"""Validate that status is success."""
|
172
|
-
if v != 'success':
|
173
|
-
raise ValueError(f"Expected status 'success', got '{v}'")
|
174
|
-
return v
|
175
|
-
|
176
|
-
@field_validator('prices')
|
177
|
-
@classmethod
|
178
|
-
def validate_prices_not_empty(cls, v):
|
179
|
-
"""Validate that prices dict is not empty."""
|
180
|
-
if not v:
|
181
|
-
raise ValueError("Prices dictionary cannot be empty")
|
182
|
-
return v
|
183
|
-
|
184
|
-
def get_usd_price(self) -> Optional[Decimal]:
|
185
|
-
"""Get USD price as Decimal."""
|
186
|
-
usd_price = self.prices.get('USD')
|
187
|
-
if usd_price:
|
188
|
-
try:
|
189
|
-
return Decimal(usd_price)
|
190
|
-
except:
|
191
|
-
return None
|
192
|
-
return None
|