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.
Files changed (125) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
  3. django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
  4. django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
  5. django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
  6. django_cfg/apps/payments/admin/__init__.py +23 -0
  7. django_cfg/apps/payments/admin/api_keys_admin.py +347 -0
  8. django_cfg/apps/payments/admin/balance_admin.py +434 -0
  9. django_cfg/apps/payments/admin/currencies_admin.py +186 -0
  10. django_cfg/apps/payments/admin/filters.py +259 -0
  11. django_cfg/apps/payments/admin/payments_admin.py +142 -0
  12. django_cfg/apps/payments/admin/subscriptions_admin.py +227 -0
  13. django_cfg/apps/payments/admin/tariffs_admin.py +199 -0
  14. django_cfg/apps/payments/config/__init__.py +65 -0
  15. django_cfg/apps/payments/config/module.py +70 -0
  16. django_cfg/apps/payments/config/providers.py +115 -0
  17. django_cfg/apps/payments/config/settings.py +96 -0
  18. django_cfg/apps/payments/config/utils.py +52 -0
  19. django_cfg/apps/payments/decorators.py +291 -0
  20. django_cfg/apps/payments/management/__init__.py +3 -0
  21. django_cfg/apps/payments/management/commands/README.md +178 -0
  22. django_cfg/apps/payments/management/commands/__init__.py +3 -0
  23. django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
  24. django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
  25. django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
  26. django_cfg/apps/payments/managers/currency_manager.py +65 -14
  27. django_cfg/apps/payments/middleware/api_access.py +294 -0
  28. django_cfg/apps/payments/middleware/rate_limiting.py +216 -0
  29. django_cfg/apps/payments/middleware/usage_tracking.py +296 -0
  30. django_cfg/apps/payments/migrations/0001_initial.py +125 -11
  31. django_cfg/apps/payments/models/__init__.py +18 -0
  32. django_cfg/apps/payments/models/api_keys.py +2 -2
  33. django_cfg/apps/payments/models/balance.py +2 -2
  34. django_cfg/apps/payments/models/base.py +16 -0
  35. django_cfg/apps/payments/models/events.py +2 -2
  36. django_cfg/apps/payments/models/payments.py +112 -2
  37. django_cfg/apps/payments/models/subscriptions.py +2 -2
  38. django_cfg/apps/payments/services/__init__.py +64 -7
  39. django_cfg/apps/payments/services/billing/__init__.py +8 -0
  40. django_cfg/apps/payments/services/cache/__init__.py +15 -0
  41. django_cfg/apps/payments/services/cache/base.py +30 -0
  42. django_cfg/apps/payments/services/cache/simple_cache.py +135 -0
  43. django_cfg/apps/payments/services/core/__init__.py +17 -0
  44. django_cfg/apps/payments/services/core/balance_service.py +447 -0
  45. django_cfg/apps/payments/services/core/fallback_service.py +432 -0
  46. django_cfg/apps/payments/services/core/payment_service.py +576 -0
  47. django_cfg/apps/payments/services/core/subscription_service.py +614 -0
  48. django_cfg/apps/payments/services/internal_types.py +297 -0
  49. django_cfg/apps/payments/services/middleware/__init__.py +8 -0
  50. django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
  51. django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
  52. django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
  53. django_cfg/apps/payments/services/providers/__init__.py +22 -0
  54. django_cfg/apps/payments/services/providers/base.py +137 -0
  55. django_cfg/apps/payments/services/providers/cryptapi.py +273 -0
  56. django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
  57. django_cfg/apps/payments/services/providers/nowpayments.py +293 -0
  58. django_cfg/apps/payments/services/providers/registry.py +103 -0
  59. django_cfg/apps/payments/services/security/__init__.py +34 -0
  60. django_cfg/apps/payments/services/security/error_handler.py +637 -0
  61. django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
  62. django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
  63. django_cfg/apps/payments/services/validators/__init__.py +8 -0
  64. django_cfg/apps/payments/signals/__init__.py +13 -0
  65. django_cfg/apps/payments/signals/api_key_signals.py +160 -0
  66. django_cfg/apps/payments/signals/payment_signals.py +128 -0
  67. django_cfg/apps/payments/signals/subscription_signals.py +196 -0
  68. django_cfg/apps/payments/tasks/__init__.py +12 -0
  69. django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
  70. django_cfg/apps/payments/urls.py +5 -5
  71. django_cfg/apps/payments/utils/__init__.py +45 -0
  72. django_cfg/apps/payments/utils/billing_utils.py +342 -0
  73. django_cfg/apps/payments/utils/config_utils.py +245 -0
  74. django_cfg/apps/payments/utils/middleware_utils.py +228 -0
  75. django_cfg/apps/payments/utils/validation_utils.py +94 -0
  76. django_cfg/apps/payments/views/payment_views.py +40 -2
  77. django_cfg/apps/payments/views/webhook_views.py +266 -0
  78. django_cfg/apps/payments/viewsets.py +65 -0
  79. django_cfg/apps/support/signals.py +16 -4
  80. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  81. django_cfg/cli/README.md +2 -2
  82. django_cfg/cli/commands/create_project.py +1 -1
  83. django_cfg/cli/commands/info.py +1 -1
  84. django_cfg/cli/main.py +1 -1
  85. django_cfg/cli/utils.py +5 -5
  86. django_cfg/core/config.py +18 -4
  87. django_cfg/models/payments.py +546 -0
  88. django_cfg/models/revolution.py +1 -1
  89. django_cfg/models/tasks.py +51 -2
  90. django_cfg/modules/base.py +12 -6
  91. django_cfg/modules/django_currency/README.md +104 -269
  92. django_cfg/modules/django_currency/__init__.py +99 -41
  93. django_cfg/modules/django_currency/clients/__init__.py +11 -0
  94. django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
  95. django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
  96. django_cfg/modules/django_currency/core/__init__.py +42 -0
  97. django_cfg/modules/django_currency/core/converter.py +169 -0
  98. django_cfg/modules/django_currency/core/exceptions.py +28 -0
  99. django_cfg/modules/django_currency/core/models.py +54 -0
  100. django_cfg/modules/django_currency/database/__init__.py +25 -0
  101. django_cfg/modules/django_currency/database/database_loader.py +507 -0
  102. django_cfg/modules/django_currency/utils/__init__.py +9 -0
  103. django_cfg/modules/django_currency/utils/cache.py +92 -0
  104. django_cfg/modules/django_email.py +42 -4
  105. django_cfg/modules/django_unfold/dashboard.py +20 -0
  106. django_cfg/registry/core.py +10 -0
  107. django_cfg/template_archive/__init__.py +0 -0
  108. django_cfg/template_archive/django_sample.zip +0 -0
  109. {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/METADATA +11 -6
  110. {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/RECORD +113 -50
  111. django_cfg/apps/agents/examples/__init__.py +0 -3
  112. django_cfg/apps/agents/examples/simple_example.py +0 -161
  113. django_cfg/apps/knowbase/examples/__init__.py +0 -3
  114. django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
  115. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
  116. django_cfg/apps/payments/services/base.py +0 -68
  117. django_cfg/apps/payments/services/nowpayments.py +0 -78
  118. django_cfg/apps/payments/services/providers.py +0 -77
  119. django_cfg/apps/payments/services/redis_service.py +0 -215
  120. django_cfg/modules/django_currency/cache.py +0 -430
  121. django_cfg/modules/django_currency/converter.py +0 -324
  122. django_cfg/modules/django_currency/service.py +0 -277
  123. {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/WHEEL +0 -0
  124. {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/entry_points.txt +0 -0
  125. {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
+ }