django-cfg 1.2.27__py3-none-any.whl → 1.2.31__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 (138) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/__init__.py +3 -2
  3. django_cfg/apps/payments/admin/balance_admin.py +18 -18
  4. django_cfg/apps/payments/admin/currencies_admin.py +319 -131
  5. django_cfg/apps/payments/admin/payments_admin.py +15 -4
  6. django_cfg/apps/payments/config/module.py +2 -2
  7. django_cfg/apps/payments/config/utils.py +2 -2
  8. django_cfg/apps/payments/decorators.py +2 -2
  9. django_cfg/apps/payments/management/commands/README.md +95 -127
  10. django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
  11. django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
  12. django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
  13. django_cfg/apps/payments/managers/__init__.py +3 -2
  14. django_cfg/apps/payments/managers/balance_manager.py +2 -2
  15. django_cfg/apps/payments/managers/currency_manager.py +272 -49
  16. django_cfg/apps/payments/managers/payment_manager.py +161 -13
  17. django_cfg/apps/payments/middleware/api_access.py +2 -2
  18. django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
  19. django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
  20. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
  21. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
  22. django_cfg/apps/payments/models/__init__.py +3 -2
  23. django_cfg/apps/payments/models/currencies.py +187 -71
  24. django_cfg/apps/payments/models/payments.py +3 -2
  25. django_cfg/apps/payments/serializers/__init__.py +3 -2
  26. django_cfg/apps/payments/serializers/currencies.py +20 -12
  27. django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
  28. django_cfg/apps/payments/services/core/balance_service.py +2 -2
  29. django_cfg/apps/payments/services/core/fallback_service.py +2 -2
  30. django_cfg/apps/payments/services/core/payment_service.py +3 -6
  31. django_cfg/apps/payments/services/core/subscription_service.py +4 -7
  32. django_cfg/apps/payments/services/internal_types.py +171 -7
  33. django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
  34. django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
  35. django_cfg/apps/payments/services/providers/base.py +144 -43
  36. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
  37. django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
  38. django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
  39. django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
  40. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
  41. django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
  42. django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
  43. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
  44. django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
  45. django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
  46. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
  47. django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
  48. django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
  49. django_cfg/apps/payments/services/providers/registry.py +294 -11
  50. django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
  51. django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
  52. django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
  53. django_cfg/apps/payments/services/security/error_handler.py +6 -8
  54. django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
  55. django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
  56. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  57. django_cfg/apps/payments/signals/payment_signals.py +11 -5
  58. django_cfg/apps/payments/signals/subscription_signals.py +2 -2
  59. django_cfg/apps/payments/static/payments/css/payments.css +340 -0
  60. django_cfg/apps/payments/static/payments/js/notifications.js +202 -0
  61. django_cfg/apps/payments/static/payments/js/payment-utils.js +318 -0
  62. django_cfg/apps/payments/static/payments/js/theme.js +86 -0
  63. django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
  64. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
  65. django_cfg/apps/payments/templates/payments/base.html +182 -0
  66. django_cfg/apps/payments/templates/payments/components/payment_card.html +201 -0
  67. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +109 -0
  68. django_cfg/apps/payments/templates/payments/components/progress_bar.html +43 -0
  69. django_cfg/apps/payments/templates/payments/components/provider_stats.html +40 -0
  70. django_cfg/apps/payments/templates/payments/components/status_badge.html +34 -0
  71. django_cfg/apps/payments/templates/payments/components/status_overview.html +148 -0
  72. django_cfg/apps/payments/templates/payments/dashboard.html +258 -0
  73. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
  74. django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
  75. django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
  76. django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
  77. django_cfg/apps/payments/templates/payments/stats.html +261 -0
  78. django_cfg/apps/payments/templates/payments/test.html +213 -0
  79. django_cfg/apps/payments/templatetags/__init__.py +1 -0
  80. django_cfg/apps/payments/templatetags/payments_tags.py +315 -0
  81. django_cfg/apps/payments/urls.py +3 -1
  82. django_cfg/apps/payments/urls_admin.py +58 -0
  83. django_cfg/apps/payments/utils/__init__.py +1 -3
  84. django_cfg/apps/payments/utils/billing_utils.py +2 -2
  85. django_cfg/apps/payments/utils/config_utils.py +2 -8
  86. django_cfg/apps/payments/utils/validation_utils.py +2 -2
  87. django_cfg/apps/payments/views/__init__.py +3 -2
  88. django_cfg/apps/payments/views/currency_views.py +31 -20
  89. django_cfg/apps/payments/views/payment_views.py +2 -2
  90. django_cfg/apps/payments/views/templates/__init__.py +25 -0
  91. django_cfg/apps/payments/views/templates/ajax.py +451 -0
  92. django_cfg/apps/payments/views/templates/base.py +212 -0
  93. django_cfg/apps/payments/views/templates/dashboard.py +60 -0
  94. django_cfg/apps/payments/views/templates/payment_detail.py +102 -0
  95. django_cfg/apps/payments/views/templates/payment_management.py +158 -0
  96. django_cfg/apps/payments/views/templates/qr_code.py +174 -0
  97. django_cfg/apps/payments/views/templates/stats.py +244 -0
  98. django_cfg/apps/payments/views/templates/utils.py +181 -0
  99. django_cfg/apps/payments/views/webhook_views.py +2 -2
  100. django_cfg/apps/payments/viewsets.py +3 -2
  101. django_cfg/apps/tasks/urls.py +0 -2
  102. django_cfg/apps/tasks/urls_admin.py +14 -0
  103. django_cfg/apps/urls.py +6 -3
  104. django_cfg/core/config.py +35 -0
  105. django_cfg/models/payments.py +2 -8
  106. django_cfg/modules/django_currency/__init__.py +16 -11
  107. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  108. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  109. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  110. django_cfg/modules/django_currency/core/__init__.py +1 -7
  111. django_cfg/modules/django_currency/core/converter.py +18 -23
  112. django_cfg/modules/django_currency/core/models.py +122 -11
  113. django_cfg/modules/django_currency/database/__init__.py +4 -4
  114. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  115. django_cfg/modules/django_unfold/dashboard.py +7 -2
  116. django_cfg/registry/core.py +1 -0
  117. django_cfg/template_archive/.gitignore +1 -0
  118. django_cfg/template_archive/django_sample.zip +0 -0
  119. django_cfg/templates/admin/components/action_grid.html +9 -9
  120. django_cfg/templates/admin/components/metric_card.html +5 -5
  121. django_cfg/templates/admin/components/status_badge.html +2 -2
  122. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  123. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  124. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  125. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  126. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/METADATA +13 -18
  127. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/RECORD +130 -83
  128. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  129. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  130. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  131. django_cfg/apps/payments/services/providers/cryptomus.py +0 -310
  132. django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
  133. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  134. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  135. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  136. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
  137. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
  138. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -4,30 +4,42 @@ Provider registry for managing payment providers.
4
4
  Central registry with lazy loading and typed configuration.
5
5
  """
6
6
 
7
- import logging
8
- from typing import Optional, List
7
+ import time
8
+ from typing import Optional, List, Dict, Any
9
+ from datetime import datetime, timedelta
10
+ from django.core.cache import cache
11
+ from django.utils import timezone
9
12
 
10
13
  from .base import PaymentProvider
11
- from .nowpayments import NowPaymentsProvider, NowPaymentsConfig
12
- from .cryptapi import CryptAPIProvider, CryptAPIConfig
13
- from .cryptomus import CryptomusProvider, CryptomusConfig
14
+ from ...utils.config_utils import get_payments_config
15
+ from .nowpayments.provider import NowPaymentsProvider
16
+ from .nowpayments.models import NowPaymentsConfig
17
+ from .cryptapi.provider import CryptAPIProvider
18
+ from .cryptapi.models import CryptAPIConfig
19
+ from .cryptomus.provider import CryptomusProvider
20
+ from .cryptomus.models import CryptomusConfig
21
+ from .stripe.provider import StripeProvider
22
+ from .stripe.models import StripeConfig
23
+ from django_cfg.modules.django_logger import get_logger
14
24
 
15
- logger = logging.getLogger(__name__)
25
+ logger = get_logger("provider_registry")
16
26
 
17
27
 
18
28
  class ProviderRegistry:
19
29
  """Central registry for payment providers with typed configs."""
20
30
 
21
31
  def __init__(self):
22
- """Initialize registry with lazy loading."""
32
+ """Initialize registry with lazy loading and health monitoring."""
23
33
  self._providers: dict[str, PaymentProvider] = {}
24
34
  self._provider_configs: dict[str, dict] = {}
35
+ self._health_cache: dict[str, dict] = {}
36
+ self._fallback_order: List[str] = [] # Provider preference order
25
37
  self._load_configurations()
38
+ self._initialize_health_monitoring()
26
39
 
27
40
  def _load_configurations(self) -> None:
28
41
  """Load provider configurations."""
29
42
  try:
30
- from ...utils.config_utils import get_payments_config
31
43
  config = get_payments_config()
32
44
 
33
45
  self._provider_configs = {}
@@ -52,8 +64,8 @@ class ProviderRegistry:
52
64
  config = CryptomusConfig(**config_dict)
53
65
  return CryptomusProvider(config)
54
66
  elif name == 'stripe':
55
- # TODO: Implement StripeProvider with StripeConfig
56
- return None
67
+ config = StripeConfig(**config_dict)
68
+ return StripeProvider(config)
57
69
  else:
58
70
  logger.warning(f"Unknown provider type: {name}")
59
71
  return None
@@ -92,12 +104,283 @@ class ProviderRegistry:
92
104
  active = []
93
105
  for name in self.list_providers():
94
106
  provider = self.get_provider(name)
95
- if provider and provider.is_enabled():
107
+ if provider and provider.enabled:
96
108
  active.append(name)
97
109
  return active
98
110
 
111
+ def _initialize_health_monitoring(self) -> None:
112
+ """Initialize health monitoring for all providers."""
113
+ try:
114
+ # Set up fallback order based on configuration or defaults
115
+ available_providers = self.list_providers()
116
+
117
+ # Default priority: NowPayments -> CryptAPI -> Cryptomus -> Stripe
118
+ priority_order = ['nowpayments', 'cryptapi', 'cryptomus', 'stripe']
119
+
120
+ self._fallback_order = [p for p in priority_order if p in available_providers]
121
+
122
+ # Add any other providers not in priority list
123
+ for provider in available_providers:
124
+ if provider not in self._fallback_order:
125
+ self._fallback_order.append(provider)
126
+
127
+ logger.info(f"Initialized provider fallback order: {self._fallback_order}")
128
+
129
+ except Exception as e:
130
+ logger.error(f"Error initializing health monitoring: {e}")
131
+ self._fallback_order = []
132
+
133
+ async def health_check_all(self) -> Dict[str, Dict[str, Any]]:
134
+ """
135
+ Check health of all providers with performance metrics.
136
+
137
+ Returns:
138
+ Dict mapping provider names to health status
139
+ """
140
+ results = {}
141
+
142
+ for provider_name in self.list_providers():
143
+ try:
144
+ provider = self.get_provider(provider_name)
145
+ if not provider:
146
+ results[provider_name] = {
147
+ 'status': 'unavailable',
148
+ 'error': 'Provider not loaded',
149
+ 'last_check': timezone.now().isoformat()
150
+ }
151
+ continue
152
+
153
+ # Measure response time
154
+ start_time = time.time()
155
+
156
+ try:
157
+ # Use get_supported_currencies as health check endpoint
158
+ health_response = provider.get_supported_currencies()
159
+ response_time = int((time.time() - start_time) * 1000) # ms
160
+
161
+ if health_response.success:
162
+ status = 'healthy'
163
+ error = None
164
+ else:
165
+ status = 'degraded'
166
+ error = health_response.error_message
167
+
168
+ except Exception as provider_error:
169
+ response_time = int((time.time() - start_time) * 1000) # ms
170
+ status = 'unhealthy'
171
+ error = str(provider_error)
172
+
173
+ # Cache health status
174
+ health_data = {
175
+ 'status': status,
176
+ 'response_time_ms': response_time,
177
+ 'error': error,
178
+ 'last_check': timezone.now().isoformat(),
179
+ 'provider_enabled': provider.enabled
180
+ }
181
+
182
+ # Store in cache for 5 minutes
183
+ cache_key = f"provider_health:{provider_name}"
184
+ cache.set(cache_key, health_data, timeout=300)
185
+
186
+ results[provider_name] = health_data
187
+
188
+ except Exception as e:
189
+ logger.error(f"Health check failed for {provider_name}: {e}")
190
+ results[provider_name] = {
191
+ 'status': 'error',
192
+ 'error': str(e),
193
+ 'last_check': timezone.now().isoformat()
194
+ }
195
+
196
+ return results
197
+
198
+ def get_healthy_providers(self, operation: str = None) -> List[str]:
199
+ """
200
+ Get list of healthy providers in fallback order.
201
+
202
+ Args:
203
+ operation: Specific operation (e.g., 'payment_creation', 'webhook')
204
+
205
+ Returns:
206
+ List of provider names sorted by health and priority
207
+ """
208
+ healthy_providers = []
209
+
210
+ for provider_name in self._fallback_order:
211
+ # Check cached health status
212
+ cache_key = f"provider_health:{provider_name}"
213
+ health_data = cache.get(cache_key)
214
+
215
+ if health_data and health_data.get('status') in ['healthy', 'degraded']:
216
+ provider = self.get_provider(provider_name)
217
+ if provider and provider.enabled:
218
+ healthy_providers.append(provider_name)
219
+
220
+ return healthy_providers
221
+
222
+ def get_provider_with_fallback(self, preferred_provider: str = None, operation: str = None) -> Optional[PaymentProvider]:
223
+ """
224
+ Get provider with automatic fallback to healthy alternatives.
225
+
226
+ Args:
227
+ preferred_provider: Preferred provider name
228
+ operation: Operation type for provider selection
229
+
230
+ Returns:
231
+ PaymentProvider instance or None if all providers are down
232
+ """
233
+ # Start with preferred provider if specified and healthy
234
+ if preferred_provider:
235
+ provider = self.get_provider(preferred_provider)
236
+ if provider and provider.enabled:
237
+ # Quick health check from cache
238
+ cache_key = f"provider_health:{preferred_provider}"
239
+ health_data = cache.get(cache_key)
240
+
241
+ if not health_data or health_data.get('status') in ['healthy', 'degraded']:
242
+ logger.info(f"Using preferred provider: {preferred_provider}")
243
+ return provider
244
+ else:
245
+ logger.warning(f"Preferred provider {preferred_provider} is unhealthy, falling back")
246
+
247
+ # Fallback to healthy providers in order
248
+ healthy_providers = self.get_healthy_providers(operation)
249
+
250
+ for provider_name in healthy_providers:
251
+ provider = self.get_provider(provider_name)
252
+ if provider:
253
+ logger.info(f"Using fallback provider: {provider_name}")
254
+ return provider
255
+
256
+ logger.error("No healthy providers available!")
257
+ return None
258
+
259
+ def record_provider_performance(self, provider_name: str, operation: str,
260
+ response_time_ms: int, success: bool) -> None:
261
+ """
262
+ Record provider performance metrics.
263
+
264
+ Args:
265
+ provider_name: Name of the provider
266
+ operation: Operation performed (e.g., 'create_payment', 'check_status')
267
+ response_time_ms: Response time in milliseconds
268
+ success: Whether operation was successful
269
+ """
270
+ try:
271
+ # Store performance metrics in cache
272
+ metric_key = f"provider_metrics:{provider_name}:{operation}"
273
+
274
+ # Get current metrics
275
+ current_metrics = cache.get(metric_key, {
276
+ 'total_requests': 0,
277
+ 'successful_requests': 0,
278
+ 'average_response_time': 0,
279
+ 'last_updated': timezone.now().isoformat()
280
+ })
281
+
282
+ # Update metrics
283
+ total_requests = current_metrics['total_requests'] + 1
284
+ successful_requests = current_metrics['successful_requests'] + (1 if success else 0)
285
+
286
+ # Calculate rolling average response time
287
+ current_avg = current_metrics['average_response_time']
288
+ new_avg = ((current_avg * current_metrics['total_requests']) + response_time_ms) / total_requests
289
+
290
+ updated_metrics = {
291
+ 'total_requests': total_requests,
292
+ 'successful_requests': successful_requests,
293
+ 'success_rate': (successful_requests / total_requests) * 100,
294
+ 'average_response_time': int(new_avg),
295
+ 'last_response_time': response_time_ms,
296
+ 'last_updated': timezone.now().isoformat()
297
+ }
298
+
299
+ # Store for 24 hours
300
+ cache.set(metric_key, updated_metrics, timeout=86400)
301
+
302
+ # Log performance issues
303
+ if not success:
304
+ logger.warning(f"Provider {provider_name} operation {operation} failed (response time: {response_time_ms}ms)")
305
+ elif response_time_ms > 5000: # > 5 seconds
306
+ logger.warning(f"Provider {provider_name} operation {operation} slow (response time: {response_time_ms}ms)")
307
+
308
+ except Exception as e:
309
+ logger.error(f"Error recording provider performance: {e}")
310
+
311
+ def get_provider_metrics(self, provider_name: str = None) -> Dict[str, Dict[str, Any]]:
312
+ """
313
+ Get performance metrics for providers.
314
+
315
+ Args:
316
+ provider_name: Specific provider or None for all providers
317
+
318
+ Returns:
319
+ Dict of provider metrics
320
+ """
321
+ if provider_name:
322
+ providers_to_check = [provider_name]
323
+ else:
324
+ providers_to_check = self.list_providers()
325
+
326
+ metrics = {}
327
+
328
+ for provider in providers_to_check:
329
+ provider_metrics = {}
330
+
331
+ # Common operations to check
332
+ operations = ['create_payment', 'check_status', 'process_webhook', 'get_currencies']
333
+
334
+ for operation in operations:
335
+ metric_key = f"provider_metrics:{provider}:{operation}"
336
+ operation_metrics = cache.get(metric_key)
337
+
338
+ if operation_metrics:
339
+ provider_metrics[operation] = operation_metrics
340
+
341
+ if provider_metrics:
342
+ metrics[provider] = provider_metrics
343
+
344
+ return metrics
345
+
99
346
  def reload_providers(self) -> None:
100
347
  """Reload all providers from configuration."""
101
348
  logger.info("Reloading providers from configuration")
102
349
  self._providers.clear()
103
350
  self._load_configurations()
351
+
352
+
353
+ # Global singleton instance
354
+ _registry_instance = None
355
+
356
+ def get_provider_registry() -> ProviderRegistry:
357
+ """Get global provider registry instance."""
358
+ global _registry_instance
359
+ if _registry_instance is None:
360
+ _registry_instance = ProviderRegistry()
361
+ return _registry_instance
362
+
363
+
364
+ def get_payment_provider(provider_name: str) -> Optional[PaymentProvider]:
365
+ """
366
+ Get payment provider instance by name.
367
+
368
+ Args:
369
+ provider_name: Name of provider (e.g. 'nowpayments', 'stripe')
370
+
371
+ Returns:
372
+ Provider instance or None if not found
373
+ """
374
+ registry = get_provider_registry()
375
+ return registry.get_provider(provider_name)
376
+
377
+
378
+ def get_available_providers() -> List[str]:
379
+ """
380
+ Get list of available provider names.
381
+
382
+ Returns:
383
+ List of provider names that are configured
384
+ """
385
+ registry = get_provider_registry()
386
+ return registry.list_providers()
@@ -0,0 +1,4 @@
1
+ from .provider import StripeProvider
2
+ from .models import StripeConfig, StripeCurrency, StripeNetwork
3
+
4
+ __all__ = ['StripeProvider', 'StripeConfig', 'StripeCurrency', 'StripeNetwork']
@@ -0,0 +1,184 @@
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
+
7
+
8
+ class StripeConfig(ProviderConfig):
9
+ """Stripe provider configuration with Pydantic v2."""
10
+
11
+ webhook_secret: Optional[str] = Field(None, description="Webhook endpoint secret")
12
+ success_url: Optional[str] = Field(None, description="Payment success redirect URL")
13
+ cancel_url: Optional[str] = Field(None, description="Payment cancel redirect URL")
14
+
15
+ @field_validator('api_key')
16
+ @classmethod
17
+ def validate_api_key(cls, v: str) -> str:
18
+ if not v or not v.startswith(('sk_test_', 'sk_live_')):
19
+ raise ValueError("Stripe API key must start with sk_test_ or sk_live_")
20
+ return v
21
+
22
+
23
+ class StripeCurrency(BaseModel):
24
+ """Stripe specific currency model."""
25
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
26
+
27
+ currency_code: str = Field(..., description="Currency symbol (e.g., USD, EUR)")
28
+ name: str = Field(..., description="Full currency name")
29
+ decimal_digits: int = Field(..., description="Number of decimal digits")
30
+ min_amount: Optional[Decimal] = Field(None, description="Minimum charge amount")
31
+ max_amount: Optional[Decimal] = Field(None, description="Maximum charge amount")
32
+ is_zero_decimal: bool = Field(False, description="Zero-decimal currency (like JPY)")
33
+
34
+
35
+ class StripeNetwork(BaseModel):
36
+ """Stripe network model (not applicable for fiat payments)."""
37
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
38
+
39
+ code: str = Field(..., description="Network code (always 'stripe')")
40
+ name: str = Field(..., description="Network display name")
41
+ currency: str = Field(..., description="Currency this network belongs to")
42
+
43
+
44
+ class StripePaymentIntentRequest(BaseModel):
45
+ """Stripe PaymentIntent creation request."""
46
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
47
+
48
+ amount: int = Field(..., description="Amount in smallest currency unit")
49
+ currency: str = Field(..., description="Three-letter ISO currency code")
50
+ payment_method_types: List[str] = Field(default=["card"], description="Payment method types")
51
+ confirm: bool = Field(False, description="Confirm PaymentIntent immediately")
52
+ capture_method: str = Field("automatic", description="Capture method")
53
+ confirmation_method: str = Field("automatic", description="Confirmation method")
54
+ description: Optional[str] = Field(None, description="Payment description")
55
+ metadata: Optional[Dict[str, str]] = Field(None, description="Metadata")
56
+ receipt_email: Optional[str] = Field(None, description="Receipt email")
57
+ return_url: Optional[str] = Field(None, description="Return URL")
58
+ success_url: Optional[str] = Field(None, description="Success URL")
59
+ cancel_url: Optional[str] = Field(None, description="Cancel URL")
60
+
61
+
62
+ class StripePaymentIntentResponse(BaseModel):
63
+ """Stripe PaymentIntent response."""
64
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
65
+
66
+ id: str = Field(..., description="PaymentIntent ID")
67
+ object: str = Field(..., description="Object type")
68
+ amount: int = Field(..., description="Amount in smallest currency unit")
69
+ currency: str = Field(..., description="Currency code")
70
+ status: str = Field(..., description="PaymentIntent status")
71
+ client_secret: str = Field(..., description="Client secret for confirmation")
72
+ created: int = Field(..., description="Creation timestamp")
73
+ description: Optional[str] = Field(None, description="Payment description")
74
+ metadata: Dict[str, str] = Field(default_factory=dict, description="Metadata")
75
+ payment_method: Optional[str] = Field(None, description="Payment method ID")
76
+ receipt_email: Optional[str] = Field(None, description="Receipt email")
77
+ latest_charge: Optional[str] = Field(None, description="Latest charge ID")
78
+
79
+
80
+ class StripeWebhookEvent(BaseModel):
81
+ """Stripe webhook event data."""
82
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
83
+
84
+ id: str = Field(..., description="Event ID")
85
+ object: str = Field(..., description="Object type")
86
+ api_version: str = Field(..., description="API version")
87
+ created: int = Field(..., description="Creation timestamp")
88
+ type: str = Field(..., description="Event type")
89
+ data: Dict[str, Any] = Field(..., description="Event data")
90
+ livemode: bool = Field(..., description="Live mode flag")
91
+ pending_webhooks: int = Field(..., description="Pending webhooks count")
92
+ request: Optional[Dict[str, Any]] = Field(None, description="Request info")
93
+
94
+
95
+ class StripeCharge(BaseModel):
96
+ """Stripe charge object."""
97
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
98
+
99
+ id: str = Field(..., description="Charge ID")
100
+ object: str = Field(..., description="Object type")
101
+ amount: int = Field(..., description="Amount charged")
102
+ currency: str = Field(..., description="Currency code")
103
+ status: str = Field(..., description="Charge status")
104
+ created: int = Field(..., description="Creation timestamp")
105
+ description: Optional[str] = Field(None, description="Charge description")
106
+ metadata: Dict[str, str] = Field(default_factory=dict, description="Metadata")
107
+ payment_intent: Optional[str] = Field(None, description="PaymentIntent ID")
108
+ receipt_email: Optional[str] = Field(None, description="Receipt email")
109
+ receipt_url: Optional[str] = Field(None, description="Receipt URL")
110
+
111
+
112
+ class StripeCustomer(BaseModel):
113
+ """Stripe customer object."""
114
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
115
+
116
+ id: str = Field(..., description="Customer ID")
117
+ object: str = Field(..., description="Object type")
118
+ created: int = Field(..., description="Creation timestamp")
119
+ email: Optional[str] = Field(None, description="Customer email")
120
+ name: Optional[str] = Field(None, description="Customer name")
121
+ phone: Optional[str] = Field(None, description="Customer phone")
122
+ metadata: Dict[str, str] = Field(default_factory=dict, description="Metadata")
123
+
124
+
125
+ class StripeError(BaseModel):
126
+ """Stripe error response."""
127
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
128
+
129
+ type: str = Field(..., description="Error type")
130
+ code: Optional[str] = Field(None, description="Error code")
131
+ message: str = Field(..., description="Error message")
132
+ param: Optional[str] = Field(None, description="Parameter causing error")
133
+ decline_code: Optional[str] = Field(None, description="Decline code")
134
+
135
+
136
+ class StripeErrorResponse(BaseModel):
137
+ """Stripe error response wrapper."""
138
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
139
+
140
+ error: StripeError = Field(..., description="Error details")
141
+
142
+
143
+ class StripeCurrenciesResponse(BaseModel):
144
+ """Stripe supported currencies response."""
145
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
146
+
147
+ currencies: List[StripeCurrency] = Field(..., description="List of supported currencies")
148
+
149
+
150
+ class StripeWebhookEndpoint(BaseModel):
151
+ """Stripe webhook endpoint configuration."""
152
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
153
+
154
+ id: str = Field(..., description="Webhook endpoint ID")
155
+ object: str = Field(..., description="Object type")
156
+ api_version: Optional[str] = Field(None, description="API version")
157
+ created: int = Field(..., description="Creation timestamp")
158
+ enabled_events: List[str] = Field(..., description="Enabled event types")
159
+ livemode: bool = Field(..., description="Live mode flag")
160
+ status: str = Field(..., description="Endpoint status")
161
+ url: str = Field(..., description="Endpoint URL")
162
+
163
+
164
+ # =============================================================================
165
+ # MONITORING & HEALTH CHECK MODELS
166
+ # =============================================================================
167
+
168
+ class StripeHealthErrorResponse(BaseModel):
169
+ """Stripe API error response schema for health checks."""
170
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
171
+
172
+ class StripeHealthError(BaseModel):
173
+ message: str = Field(..., description="Error message")
174
+ type: str = Field(..., description="Error type")
175
+
176
+ error: StripeHealthError = Field(..., description="Error details")
177
+
178
+ @field_validator('error')
179
+ @classmethod
180
+ def validate_auth_error(cls, v):
181
+ """Validate this is an authentication error (meaning API is healthy)."""
182
+ if v.type != 'invalid_request_error':
183
+ raise ValueError(f"Expected auth error, got '{v.type}'")
184
+ return v
@@ -0,0 +1,109 @@
1
+ from typing import Dict, Any, Optional, List
2
+ from decimal import Decimal
3
+
4
+ from ..base import PaymentProvider
5
+ from ...internal_types import ProviderResponse, WebhookData
6
+ from .models import StripeConfig, StripeCurrency, StripeNetwork
7
+ from django_cfg.modules.django_logger import get_logger
8
+
9
+ logger = get_logger("stripe")
10
+
11
+
12
+ class StripeProvider(PaymentProvider):
13
+ """Stripe payment provider implementation."""
14
+
15
+ name = "stripe"
16
+
17
+ def __init__(self, config: StripeConfig):
18
+ super().__init__(config)
19
+ self.config = config
20
+ self.api_key = config.api_key
21
+ self.webhook_secret = config.webhook_secret
22
+ self.base_url = "https://api.stripe.com/v1"
23
+
24
+ def create_payment(self, amount_usd: Decimal, currency_code: str, description: str = None, **kwargs) -> ProviderResponse:
25
+ """Create a payment intent with Stripe."""
26
+ # TODO: Implement Stripe payment creation
27
+ return ProviderResponse(
28
+ success=False,
29
+ error_message="Stripe provider not implemented yet"
30
+ )
31
+
32
+ def check_payment_status(self, payment_id: str) -> ProviderResponse:
33
+ """Check payment status from Stripe."""
34
+ # TODO: Implement Stripe payment status check
35
+ return ProviderResponse(
36
+ success=False,
37
+ error_message="Stripe provider not implemented yet"
38
+ )
39
+
40
+ def process_webhook(self, webhook_data: WebhookData) -> ProviderResponse:
41
+ """Process Stripe webhook."""
42
+ # TODO: Implement Stripe webhook processing
43
+ return ProviderResponse(
44
+ success=False,
45
+ error_message="Stripe provider not implemented yet"
46
+ )
47
+
48
+ def validate_webhook_signature(self, payload: str, signature: str) -> bool:
49
+ """Validate Stripe webhook signature."""
50
+ # TODO: Implement Stripe signature validation
51
+ return False
52
+
53
+ def get_supported_currencies(self) -> ProviderResponse:
54
+ """Get supported currencies from Stripe."""
55
+ # Common fiat currencies supported by Stripe
56
+ currencies = [
57
+ StripeCurrency(
58
+ currency_code='USD',
59
+ name='US Dollar',
60
+ decimal_digits=2,
61
+ min_amount=Decimal('0.50'),
62
+ is_zero_decimal=False
63
+ ),
64
+ StripeCurrency(
65
+ currency_code='EUR',
66
+ name='Euro',
67
+ decimal_digits=2,
68
+ min_amount=Decimal('0.50'),
69
+ is_zero_decimal=False
70
+ ),
71
+ StripeCurrency(
72
+ currency_code='GBP',
73
+ name='British Pound',
74
+ decimal_digits=2,
75
+ min_amount=Decimal('0.30'),
76
+ is_zero_decimal=False
77
+ ),
78
+ StripeCurrency(
79
+ currency_code='JPY',
80
+ name='Japanese Yen',
81
+ decimal_digits=0,
82
+ min_amount=Decimal('50'),
83
+ is_zero_decimal=True
84
+ ),
85
+ ]
86
+
87
+ return ProviderResponse(
88
+ success=True,
89
+ data={'currencies': [c.model_dump() for c in currencies]}
90
+ )
91
+
92
+ def get_supported_networks(self, currency_code: str = None) -> ProviderResponse:
93
+ """Get supported networks (not applicable for Stripe fiat payments)."""
94
+ return ProviderResponse(
95
+ success=True,
96
+ data={'networks': {}}
97
+ )
98
+
99
+ def get_currency_network_mapping(self) -> Dict[str, List[str]]:
100
+ """Get currency network mapping (not applicable for Stripe)."""
101
+ return {}
102
+
103
+ def check_api_status(self) -> Dict[str, Any]:
104
+ """Check Stripe API status."""
105
+ # TODO: Implement actual API health check
106
+ return {
107
+ 'status': 'unknown',
108
+ 'message': 'Stripe provider not implemented yet'
109
+ }
@@ -4,21 +4,22 @@ Critical Foundation Security Component.
4
4
  """
5
5
 
6
6
  import json
7
- import logging
7
+ from django_cfg.modules.django_logger import get_logger
8
8
  import traceback
9
9
  from typing import Dict, Any, Optional, Union, Type
10
10
  from enum import Enum
11
- from datetime import datetime
11
+ from datetime import datetime, timedelta
12
12
  from pydantic import BaseModel, Field
13
13
  from django.http import JsonResponse, HttpResponse
14
14
  from django.utils import timezone
15
15
  from django.conf import settings
16
- from .payment_notifications import payment_notifications
17
16
  from django.core.cache import cache
17
+ from django.db.models import Count
18
18
 
19
- from ..models.events import PaymentEvent
19
+ from .payment_notifications import payment_notifications
20
+ from ...models.events import PaymentEvent
20
21
 
21
- logger = logging.getLogger(__name__)
22
+ logger = get_logger("error_handler")
22
23
 
23
24
 
24
25
  class ErrorSeverity(Enum):
@@ -593,9 +594,6 @@ class CentralizedErrorHandler:
593
594
  def get_error_statistics(self, hours: int = 24) -> Dict[str, Any]:
594
595
  """Get error statistics for monitoring dashboard."""
595
596
 
596
- from datetime import timedelta
597
- from django.db.models import Count
598
-
599
597
  since = timezone.now() - timedelta(hours=hours)
600
598
 
601
599
  # Get error events from database