django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.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,4 +0,0 @@
1
- from .provider import CryptAPIProvider
2
- from .models import CryptAPIConfig, CryptAPICurrency, CryptAPINetwork
3
-
4
- __all__ = ['CryptAPIProvider', 'CryptAPIConfig', 'CryptAPICurrency', 'CryptAPINetwork']
@@ -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