django-cfg 1.2.29__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,372 +0,0 @@
1
- """
2
- Provider Health Monitoring System.
3
-
4
- Monitors the health of all payment providers and provides
5
- fallback mechanisms when providers are unavailable.
6
- """
7
-
8
- 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
@@ -1,273 +0,0 @@
1
- """
2
- CryptAPI provider implementation.
3
-
4
- Crypto payment provider using CryptAPI service.
5
- """
6
-
7
- import logging
8
- import requests
9
- import secrets
10
- import string
11
- from typing import Optional, List
12
- from decimal import Decimal
13
- from pydantic import BaseModel, Field
14
-
15
- from .base import PaymentProvider
16
- from ..internal_types import ProviderResponse, WebhookData
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class CryptAPIConfig(BaseModel):
22
- """CryptAPI provider configuration."""
23
- own_address: str = Field(..., description="Your cryptocurrency address")
24
- callback_url: str = Field(..., description="Webhook callback URL")
25
- convert_payments: bool = Field(default=True, description="Auto-convert payments")
26
- multi_token: bool = Field(default=True, description="Support multi-token payments")
27
- priority: str = Field(default='default', description="Transaction priority")
28
- enabled: bool = Field(default=True, description="Provider enabled")
29
-
30
-
31
- class CryptAPIException(Exception):
32
- """CryptAPI specific exception."""
33
- pass
34
-
35
-
36
- class CryptAPIProvider(PaymentProvider):
37
- """CryptAPI cryptocurrency payment provider."""
38
-
39
- CRYPTAPI_URL = 'https://api.cryptapi.io/'
40
-
41
- def __init__(self, config: CryptAPIConfig):
42
- """Initialize CryptAPI provider."""
43
- super().__init__(config.dict())
44
- self.config = config
45
- self.own_address = config.own_address
46
- self.callback_url = config.callback_url
47
- self.convert_payments = config.convert_payments
48
- self.multi_token = config.multi_token
49
- self.priority = config.priority
50
-
51
- def _make_request(self, coin: str, endpoint: str, params: Optional[dict] = None) -> Optional[dict]:
52
- """Make HTTP request to CryptAPI."""
53
- try:
54
- if coin:
55
- coin = coin.replace('/', '_')
56
- url = f"{self.CRYPTAPI_URL}{coin}/{endpoint}/"
57
- else:
58
- url = f"{self.CRYPTAPI_URL}{endpoint}/"
59
-
60
- response = requests.get(url, params=params or {}, timeout=30)
61
- response.raise_for_status()
62
-
63
- result = response.json()
64
-
65
- # Check for API errors
66
- if 'error' in result:
67
- logger.error(f"CryptAPI error: {result['error']}")
68
- return None
69
-
70
- return result
71
-
72
- except requests.exceptions.RequestException as e:
73
- logger.error(f"CryptAPI request failed: {e}")
74
- return None
75
- except Exception as e:
76
- logger.error(f"Unexpected CryptAPI error: {e}")
77
- return None
78
-
79
- def create_payment(self, payment_data: dict) -> ProviderResponse:
80
- """Create payment address via CryptAPI."""
81
- try:
82
- amount = Decimal(str(payment_data['amount']))
83
- currency = payment_data['currency'].lower()
84
- order_id = payment_data.get('order_id', f'payment_{int(amount * 100)}')
85
-
86
- # Generate secure nonce for replay attack protection
87
- security_nonce = self._generate_nonce()
88
-
89
- # Build callback URL with parameters including nonce
90
- callback_params = {
91
- 'order_id': order_id,
92
- 'amount': str(amount),
93
- 'nonce': security_nonce
94
- }
95
-
96
- # Create payment address
97
- params = {
98
- 'address': self.own_address,
99
- 'callback': self.callback_url,
100
- 'convert': 1 if self.convert_payments else 0,
101
- 'multi_token': 1 if self.multi_token else 0,
102
- 'priority': self.priority,
103
- **callback_params
104
- }
105
-
106
- response = self._make_request(currency, 'create', params)
107
-
108
- if response and 'address_in' in response:
109
- return ProviderResponse(
110
- success=True,
111
- provider_payment_id=response['address_in'], # Use address as payment ID
112
- payment_url=None, # CryptAPI doesn't provide payment URLs
113
- pay_address=response['address_in'],
114
- amount=amount,
115
- currency=currency.upper(),
116
- status='pending'
117
- )
118
- else:
119
- return ProviderResponse(
120
- success=False,
121
- error_message='Failed to create payment address'
122
- )
123
-
124
- except Exception as e:
125
- logger.error(f"CryptAPI create_payment error: {e}")
126
- return ProviderResponse(
127
- success=False,
128
- error_message=str(e)
129
- )
130
-
131
- def check_payment_status(self, payment_id: str) -> ProviderResponse:
132
- """Check payment status via CryptAPI."""
133
- try:
134
- # For CryptAPI, payment_id is the address
135
- # We need to check logs to see if payment was received
136
- # This is a limitation of CryptAPI - no direct status check by address
137
-
138
- # Return pending status as CryptAPI uses callbacks for status updates
139
- return ProviderResponse(
140
- success=True,
141
- provider_payment_id=payment_id,
142
- status='pending',
143
- pay_address=payment_id,
144
- amount=Decimal('0'), # Unknown without logs
145
- currency='unknown'
146
- )
147
-
148
- except Exception as e:
149
- logger.error(f"CryptAPI check_payment_status error: {e}")
150
- return ProviderResponse(
151
- success=False,
152
- error_message=str(e)
153
- )
154
-
155
- def process_webhook(self, payload: dict) -> WebhookData:
156
- """Process CryptAPI webhook/callback."""
157
- try:
158
- # CryptAPI sends callbacks with these parameters:
159
- # - address_in: payment address
160
- # - address_out: your address
161
- # - txid_in: transaction ID
162
- # - txid_out: forwarding transaction ID (if applicable)
163
- # - confirmations: number of confirmations
164
- # - value: amount received
165
- # - value_coin: amount in coin
166
- # - value_forwarded: amount forwarded
167
- # - coin: cryptocurrency
168
- # - pending: 0 or 1
169
-
170
- confirmations = int(payload.get('confirmations', 0))
171
- pending = int(payload.get('pending', 1))
172
-
173
- # Determine status based on confirmations and pending flag
174
- if pending == 1:
175
- status = 'pending'
176
- elif confirmations >= 1:
177
- status = 'completed'
178
- else:
179
- status = 'processing'
180
-
181
- return WebhookData(
182
- provider_payment_id=payload.get('address_in', ''),
183
- status=status,
184
- pay_amount=Decimal(str(payload.get('value_coin', 0))),
185
- actually_paid=Decimal(str(payload.get('value_coin', 0))),
186
- order_id=payload.get('order_id'), # Custom parameter we sent
187
- signature=payload.get('txid_in') # Use transaction ID as signature
188
- )
189
-
190
- except Exception as e:
191
- logger.error(f"CryptAPI webhook processing error: {e}")
192
- raise
193
-
194
- def get_supported_currencies(self) -> List[str]:
195
- """Get list of supported currencies."""
196
- try:
197
- response = self._make_request('', 'info')
198
-
199
- if response and isinstance(response, dict):
200
- # CryptAPI returns a dict with coin info
201
- return list(response.keys())
202
- else:
203
- # Fallback currencies
204
- return ['BTC', 'ETH', 'LTC', 'BCH', 'XMR', 'TRX']
205
-
206
- except Exception as e:
207
- logger.error(f"Error getting supported currencies: {e}")
208
- return ['BTC', 'ETH', 'LTC'] # Minimal fallback
209
-
210
- def get_minimum_payment_amount(self, currency_from: str, currency_to: str = 'usd') -> Optional[Decimal]:
211
- """Get minimum payment amount for currency."""
212
- try:
213
- response = self._make_request(currency_from.lower(), 'info')
214
-
215
- if response and 'minimum_transaction' in response:
216
- return Decimal(str(response['minimum_transaction']))
217
-
218
- return None
219
-
220
- except Exception as e:
221
- logger.error(f"Error getting minimum amount: {e}")
222
- return None
223
-
224
- def estimate_payment_amount(self, amount: Decimal, currency_code: str) -> Optional[dict]:
225
- """Estimate payment amount - CryptAPI doesn't provide this."""
226
- # CryptAPI doesn't have a direct estimation API
227
- # Would need to use external price APIs
228
- return None
229
-
230
- def validate_webhook(self, payload: dict, headers: Optional[dict] = None) -> bool:
231
- """Validate CryptAPI webhook."""
232
- try:
233
- # CryptAPI doesn't use HMAC signatures
234
- # Validation is done by checking if the callback came from their servers
235
- # and contains expected parameters
236
-
237
- required_fields = ['address_in', 'address_out', 'txid_in', 'value_coin', 'coin', 'confirmations']
238
-
239
- for field in required_fields:
240
- if field not in payload:
241
- logger.warning(f"Missing required field in CryptAPI webhook: {field}")
242
- return False
243
-
244
- # Basic validation passed
245
- return True
246
-
247
- except Exception as e:
248
- logger.error(f"CryptAPI webhook validation error: {e}")
249
- return False
250
-
251
- def check_api_status(self) -> bool:
252
- """Check if CryptAPI is available."""
253
- try:
254
- response = self._make_request('', 'info')
255
- return response is not None
256
- except:
257
- return False
258
-
259
- def get_logs(self, callback_url: str) -> Optional[dict]:
260
- """Get payment logs for a callback URL."""
261
- try:
262
- params = {'callback': callback_url}
263
- # Note: This would need a specific coin, but we don't know which one
264
- # This is a limitation of the current implementation
265
- return None
266
- except Exception as e:
267
- logger.error(f"Error getting logs: {e}")
268
- return None
269
-
270
- def _generate_nonce(self, length: int = 32) -> str:
271
- """Generate cryptographically secure nonce for replay attack protection."""
272
- sequence = string.ascii_letters + string.digits
273
- return ''.join([secrets.choice(sequence) for _ in range(length)])