django-cfg 1.2.22__py3-none-any.whl → 1.2.25__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/knowbase/tasks/archive_tasks.py +6 -6
- django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
- django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
- django_cfg/apps/payments/admin/__init__.py +23 -0
- django_cfg/apps/payments/admin/api_keys_admin.py +347 -0
- django_cfg/apps/payments/admin/balance_admin.py +434 -0
- django_cfg/apps/payments/admin/currencies_admin.py +186 -0
- django_cfg/apps/payments/admin/filters.py +259 -0
- django_cfg/apps/payments/admin/payments_admin.py +142 -0
- django_cfg/apps/payments/admin/subscriptions_admin.py +227 -0
- django_cfg/apps/payments/admin/tariffs_admin.py +199 -0
- django_cfg/apps/payments/config/__init__.py +65 -0
- django_cfg/apps/payments/config/module.py +70 -0
- django_cfg/apps/payments/config/providers.py +115 -0
- django_cfg/apps/payments/config/settings.py +96 -0
- django_cfg/apps/payments/config/utils.py +52 -0
- django_cfg/apps/payments/decorators.py +291 -0
- django_cfg/apps/payments/management/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/README.md +178 -0
- django_cfg/apps/payments/management/commands/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
- django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
- django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
- django_cfg/apps/payments/managers/currency_manager.py +65 -14
- django_cfg/apps/payments/middleware/api_access.py +294 -0
- django_cfg/apps/payments/middleware/rate_limiting.py +216 -0
- django_cfg/apps/payments/middleware/usage_tracking.py +296 -0
- django_cfg/apps/payments/migrations/0001_initial.py +125 -11
- django_cfg/apps/payments/models/__init__.py +18 -0
- django_cfg/apps/payments/models/api_keys.py +2 -2
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/base.py +16 -0
- django_cfg/apps/payments/models/events.py +2 -2
- django_cfg/apps/payments/models/payments.py +112 -2
- django_cfg/apps/payments/models/subscriptions.py +2 -2
- django_cfg/apps/payments/services/__init__.py +64 -7
- django_cfg/apps/payments/services/billing/__init__.py +8 -0
- django_cfg/apps/payments/services/cache/__init__.py +15 -0
- django_cfg/apps/payments/services/cache/base.py +30 -0
- django_cfg/apps/payments/services/cache/simple_cache.py +135 -0
- django_cfg/apps/payments/services/core/__init__.py +17 -0
- django_cfg/apps/payments/services/core/balance_service.py +447 -0
- django_cfg/apps/payments/services/core/fallback_service.py +432 -0
- django_cfg/apps/payments/services/core/payment_service.py +576 -0
- django_cfg/apps/payments/services/core/subscription_service.py +614 -0
- django_cfg/apps/payments/services/internal_types.py +297 -0
- django_cfg/apps/payments/services/middleware/__init__.py +8 -0
- django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
- django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
- django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
- django_cfg/apps/payments/services/providers/__init__.py +22 -0
- django_cfg/apps/payments/services/providers/base.py +137 -0
- django_cfg/apps/payments/services/providers/cryptapi.py +273 -0
- django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
- django_cfg/apps/payments/services/providers/nowpayments.py +293 -0
- django_cfg/apps/payments/services/providers/registry.py +103 -0
- django_cfg/apps/payments/services/security/__init__.py +34 -0
- django_cfg/apps/payments/services/security/error_handler.py +637 -0
- django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
- django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
- django_cfg/apps/payments/services/validators/__init__.py +8 -0
- django_cfg/apps/payments/signals/__init__.py +13 -0
- django_cfg/apps/payments/signals/api_key_signals.py +160 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -0
- django_cfg/apps/payments/signals/subscription_signals.py +196 -0
- django_cfg/apps/payments/tasks/__init__.py +12 -0
- django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
- django_cfg/apps/payments/urls.py +5 -5
- django_cfg/apps/payments/utils/__init__.py +45 -0
- django_cfg/apps/payments/utils/billing_utils.py +342 -0
- django_cfg/apps/payments/utils/config_utils.py +245 -0
- django_cfg/apps/payments/utils/middleware_utils.py +228 -0
- django_cfg/apps/payments/utils/validation_utils.py +94 -0
- django_cfg/apps/payments/views/payment_views.py +40 -2
- django_cfg/apps/payments/views/webhook_views.py +266 -0
- django_cfg/apps/payments/viewsets.py +65 -0
- django_cfg/apps/support/signals.py +16 -4
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/cli/README.md +2 -2
- django_cfg/cli/commands/create_project.py +1 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/main.py +1 -1
- django_cfg/cli/utils.py +5 -5
- django_cfg/core/config.py +18 -4
- django_cfg/models/payments.py +546 -0
- django_cfg/models/revolution.py +1 -1
- django_cfg/models/tasks.py +51 -2
- django_cfg/modules/base.py +12 -6
- django_cfg/modules/django_currency/README.md +104 -269
- django_cfg/modules/django_currency/__init__.py +99 -41
- django_cfg/modules/django_currency/clients/__init__.py +11 -0
- django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
- django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
- django_cfg/modules/django_currency/core/__init__.py +42 -0
- django_cfg/modules/django_currency/core/converter.py +169 -0
- django_cfg/modules/django_currency/core/exceptions.py +28 -0
- django_cfg/modules/django_currency/core/models.py +54 -0
- django_cfg/modules/django_currency/database/__init__.py +25 -0
- django_cfg/modules/django_currency/database/database_loader.py +507 -0
- django_cfg/modules/django_currency/utils/__init__.py +9 -0
- django_cfg/modules/django_currency/utils/cache.py +92 -0
- django_cfg/modules/django_email.py +42 -4
- django_cfg/modules/django_unfold/dashboard.py +20 -0
- django_cfg/registry/core.py +10 -0
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/METADATA +11 -6
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/RECORD +113 -50
- django_cfg/apps/agents/examples/__init__.py +0 -3
- django_cfg/apps/agents/examples/simple_example.py +0 -161
- django_cfg/apps/knowbase/examples/__init__.py +0 -3
- django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
- django_cfg/apps/payments/services/base.py +0 -68
- django_cfg/apps/payments/services/nowpayments.py +0 -78
- django_cfg/apps/payments/services/providers.py +0 -77
- django_cfg/apps/payments/services/redis_service.py +0 -215
- django_cfg/modules/django_currency/cache.py +0 -430
- django_cfg/modules/django_currency/converter.py +0 -324
- django_cfg/modules/django_currency/service.py +0 -277
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,372 @@
|
|
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
|
+
import logging
|
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 = logging.getLogger(__name__)
|
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
|
@@ -0,0 +1,22 @@
|
|
1
|
+
"""
|
2
|
+
Payment provider services.
|
3
|
+
|
4
|
+
All payment provider implementations and abstractions.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .base import PaymentProvider
|
8
|
+
from .registry import ProviderRegistry
|
9
|
+
from .nowpayments import NowPaymentsProvider, NowPaymentsConfig
|
10
|
+
from .cryptapi import CryptAPIProvider, CryptAPIConfig
|
11
|
+
from .cryptomus import CryptomusProvider, CryptomusConfig
|
12
|
+
|
13
|
+
__all__ = [
|
14
|
+
'PaymentProvider',
|
15
|
+
'ProviderRegistry',
|
16
|
+
'NowPaymentsProvider',
|
17
|
+
'NowPaymentsConfig',
|
18
|
+
'CryptAPIProvider',
|
19
|
+
'CryptAPIConfig',
|
20
|
+
'CryptomusProvider',
|
21
|
+
'CryptomusConfig',
|
22
|
+
]
|
@@ -0,0 +1,137 @@
|
|
1
|
+
"""
|
2
|
+
Base payment provider interface.
|
3
|
+
|
4
|
+
Abstract base class for all payment providers.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from typing import Optional, List
|
9
|
+
from decimal import Decimal
|
10
|
+
|
11
|
+
from ..internal_types import ProviderResponse, WebhookData
|
12
|
+
|
13
|
+
|
14
|
+
class PaymentProvider(ABC):
|
15
|
+
"""Abstract base class for payment providers."""
|
16
|
+
|
17
|
+
def __init__(self, config: dict):
|
18
|
+
"""Initialize provider with config."""
|
19
|
+
self.config = config
|
20
|
+
self.name = self.__class__.__name__.lower().replace('provider', '')
|
21
|
+
self.enabled = config.get('enabled', True)
|
22
|
+
|
23
|
+
@abstractmethod
|
24
|
+
def create_payment(self, payment_data: dict) -> ProviderResponse:
|
25
|
+
"""
|
26
|
+
Create a payment request.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
amount: Payment amount
|
30
|
+
currency: Payment currency
|
31
|
+
**kwargs: Additional parameters (order_id, description, etc.)
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Dict with payment creation result
|
35
|
+
"""
|
36
|
+
pass
|
37
|
+
|
38
|
+
@abstractmethod
|
39
|
+
def check_payment_status(self, payment_id: str) -> ProviderResponse:
|
40
|
+
"""
|
41
|
+
Check payment status.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
payment_id: Payment ID from provider
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Dict with payment status
|
48
|
+
"""
|
49
|
+
pass
|
50
|
+
|
51
|
+
@abstractmethod
|
52
|
+
def process_webhook(self, payload: dict) -> WebhookData:
|
53
|
+
"""
|
54
|
+
Process webhook payload.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
payload: Webhook data from provider
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
Dict with processed webhook data
|
61
|
+
"""
|
62
|
+
pass
|
63
|
+
|
64
|
+
@abstractmethod
|
65
|
+
def get_supported_currencies(self) -> List[str]:
|
66
|
+
"""
|
67
|
+
Get list of supported currencies.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
List of supported currency codes
|
71
|
+
"""
|
72
|
+
pass
|
73
|
+
|
74
|
+
def validate_webhook(self, payload: dict, headers: Optional[dict] = None) -> bool:
|
75
|
+
"""
|
76
|
+
Validate webhook signature and data.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
payload: Webhook data
|
80
|
+
signature: Webhook signature (if applicable)
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
True if webhook is valid
|
84
|
+
"""
|
85
|
+
# Default implementation - providers can override
|
86
|
+
return True
|
87
|
+
|
88
|
+
def get_minimum_payment_amount(self, currency_from: str, currency_to: str = 'usd') -> Optional[Decimal]:
|
89
|
+
"""
|
90
|
+
Get minimum payment amount for currency pair.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
currency_from: Source currency
|
94
|
+
currency_to: Target currency
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
Minimum payment amount or None if not supported
|
98
|
+
"""
|
99
|
+
# Optional method - providers can override
|
100
|
+
return None
|
101
|
+
|
102
|
+
def estimate_payment_amount(self, amount: Decimal, currency_code: str) -> Optional[dict]:
|
103
|
+
"""
|
104
|
+
Estimate payment amount in target currency.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
amount: Amount to estimate
|
108
|
+
currency_code: Target currency
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Dict with estimation data or None if not supported
|
112
|
+
"""
|
113
|
+
# Optional method - providers can override
|
114
|
+
return None
|
115
|
+
|
116
|
+
def check_api_status(self) -> bool:
|
117
|
+
"""
|
118
|
+
Check if provider API is available.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
True if API is available
|
122
|
+
"""
|
123
|
+
# Optional method - providers can override
|
124
|
+
return True
|
125
|
+
|
126
|
+
def is_enabled(self) -> bool:
|
127
|
+
"""Check if provider is enabled."""
|
128
|
+
return self.enabled
|
129
|
+
|
130
|
+
def get_provider_info(self) -> dict:
|
131
|
+
"""Get provider information."""
|
132
|
+
return {
|
133
|
+
'name': self.name,
|
134
|
+
'enabled': self.enabled,
|
135
|
+
'supported_currencies': self.get_supported_currencies(),
|
136
|
+
'api_status': self.check_api_status(),
|
137
|
+
}
|