django-cfg 1.2.29__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 (126) 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/tasks/webhook_processing.py +2 -2
  60. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
  61. django_cfg/apps/payments/templates/payments/base.html +4 -4
  62. django_cfg/apps/payments/templates/payments/components/payment_card.html +6 -6
  63. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +4 -4
  64. django_cfg/apps/payments/templates/payments/components/progress_bar.html +14 -7
  65. django_cfg/apps/payments/templates/payments/components/provider_stats.html +2 -2
  66. django_cfg/apps/payments/templates/payments/components/status_badge.html +8 -1
  67. django_cfg/apps/payments/templates/payments/components/status_overview.html +34 -30
  68. django_cfg/apps/payments/templates/payments/dashboard.html +202 -290
  69. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
  70. django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
  71. django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
  72. django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
  73. django_cfg/apps/payments/templates/payments/stats.html +261 -0
  74. django_cfg/apps/payments/templates/payments/test.html +213 -0
  75. django_cfg/apps/payments/urls.py +3 -1
  76. django_cfg/apps/payments/{urls_templates.py → urls_admin.py} +6 -0
  77. django_cfg/apps/payments/utils/__init__.py +1 -3
  78. django_cfg/apps/payments/utils/billing_utils.py +2 -2
  79. django_cfg/apps/payments/utils/config_utils.py +2 -8
  80. django_cfg/apps/payments/utils/validation_utils.py +2 -2
  81. django_cfg/apps/payments/views/__init__.py +3 -2
  82. django_cfg/apps/payments/views/currency_views.py +31 -20
  83. django_cfg/apps/payments/views/payment_views.py +2 -2
  84. django_cfg/apps/payments/views/templates/ajax.py +141 -2
  85. django_cfg/apps/payments/views/templates/base.py +21 -13
  86. django_cfg/apps/payments/views/templates/payment_detail.py +1 -1
  87. django_cfg/apps/payments/views/templates/payment_management.py +34 -40
  88. django_cfg/apps/payments/views/templates/stats.py +8 -4
  89. django_cfg/apps/payments/views/webhook_views.py +2 -2
  90. django_cfg/apps/payments/viewsets.py +3 -2
  91. django_cfg/apps/tasks/urls.py +0 -2
  92. django_cfg/apps/tasks/urls_admin.py +14 -0
  93. django_cfg/apps/urls.py +4 -4
  94. django_cfg/core/config.py +35 -0
  95. django_cfg/models/payments.py +2 -8
  96. django_cfg/modules/django_currency/__init__.py +16 -11
  97. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  98. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  99. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  100. django_cfg/modules/django_currency/core/__init__.py +1 -7
  101. django_cfg/modules/django_currency/core/converter.py +18 -23
  102. django_cfg/modules/django_currency/core/models.py +122 -11
  103. django_cfg/modules/django_currency/database/__init__.py +4 -4
  104. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  105. django_cfg/modules/django_unfold/dashboard.py +7 -2
  106. django_cfg/template_archive/django_sample.zip +0 -0
  107. django_cfg/templates/admin/components/action_grid.html +9 -9
  108. django_cfg/templates/admin/components/metric_card.html +5 -5
  109. django_cfg/templates/admin/components/status_badge.html +2 -2
  110. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  111. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  112. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  113. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  114. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/METADATA +2 -4
  115. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/RECORD +118 -96
  116. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  117. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  118. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  119. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  120. django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
  121. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  122. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  123. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  124. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
  125. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
  126. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -9,9 +9,9 @@ from django.utils.decorators import method_decorator
9
9
  from django.db.models import Q, Count, Sum
10
10
  from django.utils import timezone
11
11
  from datetime import timedelta
12
- import logging
12
+ from django_cfg.modules.django_logger import get_logger
13
13
 
14
- logger = logging.getLogger(__name__)
14
+ logger = get_logger("view_base")
15
15
 
16
16
 
17
17
  def superuser_required(function=None):
@@ -103,13 +103,15 @@ class PaymentStatsMixin:
103
103
  total_volume=Sum('amount_usd')
104
104
  )
105
105
 
106
- # Convert Decimal to float for JSON serialization
107
- if stats['total_volume']:
108
- stats['total_volume'] = float(stats['total_volume'])
109
- else:
110
- stats['total_volume'] = 0.0
111
-
112
- return stats
106
+ # Convert to template format
107
+ return {
108
+ 'total_payments_count': stats['total_count'] or 0,
109
+ 'pending_payments_count': stats['pending_count'] or 0,
110
+ 'confirming_payments_count': stats['confirming_count'] or 0,
111
+ 'completed_payments_count': stats['completed_count'] or 0,
112
+ 'failed_payments_count': stats['failed_count'] or 0,
113
+ 'total_volume': float(stats['total_volume'] or 0),
114
+ }
113
115
 
114
116
  def get_provider_stats(self, queryset=None):
115
117
  """Get provider-specific statistics."""
@@ -123,7 +125,8 @@ class PaymentStatsMixin:
123
125
  completed_count=Count('id', filter=Q(status='completed')),
124
126
  ).order_by('-volume')
125
127
 
126
- # Calculate success rate
128
+ # Calculate success rate and convert to list of dicts
129
+ stats_list = []
127
130
  for stat in provider_stats:
128
131
  if stat['count'] > 0:
129
132
  stat['success_rate'] = (stat['completed_count'] / stat['count']) * 100
@@ -135,8 +138,10 @@ class PaymentStatsMixin:
135
138
  stat['volume'] = float(stat['volume'])
136
139
  else:
137
140
  stat['volume'] = 0.0
141
+
142
+ stats_list.append(stat)
138
143
 
139
- return provider_stats
144
+ return stats_list
140
145
 
141
146
  def get_time_range_stats(self, days=30):
142
147
  """Get statistics for a specific time range."""
@@ -160,8 +165,11 @@ class PaymentContextMixin:
160
165
  """Get common context data used across multiple views."""
161
166
  from ...models import PaymentEvent
162
167
 
163
- # Get recent events for activity feed
164
- recent_events = PaymentEvent.objects.select_related('payment').order_by('-created_at')[:10]
168
+ # Get recent events for activity feed (if any exist)
169
+ try:
170
+ recent_events = PaymentEvent.objects.order_by('-created_at')[:10]
171
+ except Exception:
172
+ recent_events = []
165
173
 
166
174
  return {
167
175
  'recent_events': recent_events,
@@ -41,7 +41,7 @@ class PaymentDetailView(
41
41
  log_view_access('payment_detail', self.request.user, payment_id=payment.id)
42
42
 
43
43
  # Get payment events for this payment
44
- events = PaymentEvent.objects.filter(payment=payment).order_by('-created_at')
44
+ events = PaymentEvent.objects.filter(payment_id=payment.id).order_by('-created_at')
45
45
 
46
46
  # Get related payments (same user, similar amount range)
47
47
  related_payments = UniversalPayment.objects.filter(
@@ -4,6 +4,7 @@ Payment management views.
4
4
  Provides list, create, and management functionality for payments.
5
5
  """
6
6
 
7
+ import json
7
8
  from django.views.generic import TemplateView, ListView
8
9
  from .base import (
9
10
  SuperuserRequiredMixin,
@@ -11,7 +12,8 @@ from .base import (
11
12
  PaymentContextMixin,
12
13
  log_view_access
13
14
  )
14
- from ...models import UniversalPayment
15
+ from ...models import UniversalPayment, Currency, PaymentProvider
16
+ from ...services.providers.registry import ProviderRegistry
15
17
 
16
18
 
17
19
  class PaymentCreateView(
@@ -46,9 +48,20 @@ class PaymentCreateView(
46
48
  # Get common context
47
49
  common_context = self.get_common_context()
48
50
 
51
+ # Provider enum with defaults (serialize to JSON for JavaScript)
52
+ provider_defaults = {
53
+ PaymentProvider.NOWPAYMENTS.value: 'USDTTRC20',
54
+ PaymentProvider.CRYPTAPI.value: 'BTC',
55
+ PaymentProvider.CRYPTOMUS.value: 'USDTTRC20',
56
+ PaymentProvider.STRIPE.value: 'USD'
57
+ }
58
+ provider_defaults_json = json.dumps(provider_defaults)
59
+
49
60
  context.update({
50
61
  'providers': providers,
51
62
  'currencies': currencies,
63
+ 'provider_enum': PaymentProvider,
64
+ 'provider_defaults_json': provider_defaults_json,
52
65
  'default_amount': 10.0, # Default test amount
53
66
  **common_context
54
67
  })
@@ -56,44 +69,27 @@ class PaymentCreateView(
56
69
  return context
57
70
 
58
71
  def _get_available_providers(self):
59
- """Get list of available payment providers."""
60
- try:
61
- from ...services.providers.registry import ProviderRegistry
62
- providers = []
63
- for provider_name, provider_class in ProviderRegistry.get_all_providers().items():
64
- providers.append({
65
- 'name': provider_name,
66
- 'display_name': provider_name.title(),
67
- 'is_crypto': provider_name in ['nowpayments', 'cryptapi', 'cryptomus'],
68
- 'description': getattr(provider_class, '__doc__', ''),
69
- })
70
- return providers
71
- except Exception:
72
- # Fallback if registry is not available
73
- return [
74
- {'name': 'nowpayments', 'display_name': 'NowPayments', 'is_crypto': True},
75
- {'name': 'cryptapi', 'display_name': 'CryptAPI', 'is_crypto': True},
76
- {'name': 'cryptomus', 'display_name': 'Cryptomus', 'is_crypto': True},
77
- {'name': 'stripe', 'display_name': 'Stripe', 'is_crypto': False},
78
- ]
72
+ """Get list of available payment providers from registry."""
73
+ registry = ProviderRegistry()
74
+ provider_names = registry.list_providers()
75
+
76
+ providers = []
77
+ for provider_name in provider_names:
78
+ provider_instance = registry.get_provider(provider_name)
79
+ providers.append({
80
+ 'name': provider_name,
81
+ 'display_name': provider_name.title(),
82
+ 'is_crypto': provider_name in ['nowpayments', 'cryptapi', 'cryptomus'],
83
+ 'description': getattr(provider_instance.__class__, '__doc__', '') if provider_instance else '',
84
+ })
85
+ return providers
79
86
 
80
87
  def _get_available_currencies(self):
81
- """Get list of available currencies."""
82
- from ...models import Currency
83
-
84
- try:
85
- # Get currencies from database
86
- currencies = Currency.objects.filter(is_active=True).order_by('code')
87
- return [{'code': c.code, 'name': c.name} for c in currencies]
88
- except Exception:
89
- # Fallback list
90
- return [
91
- {'code': 'USD', 'name': 'US Dollar'},
92
- {'code': 'EUR', 'name': 'Euro'},
93
- {'code': 'BTC', 'name': 'Bitcoin'},
94
- {'code': 'ETH', 'name': 'Ethereum'},
95
- {'code': 'LTC', 'name': 'Litecoin'},
96
- ]
88
+ """Get list of available currencies from database."""
89
+ # Get all active currencies - both fiat and crypto
90
+ return Currency.objects.all().order_by('currency_type', 'code')
91
+
92
+
97
93
 
98
94
 
99
95
  class PaymentListView(
@@ -147,9 +143,7 @@ class PaymentListView(
147
143
 
148
144
  def _get_filter_options(self):
149
145
  """Get available options for filter dropdowns."""
150
- from django.db.models import Value
151
- from django.db.models.functions import Concat
152
-
146
+
153
147
  # Get unique statuses
154
148
  statuses = UniversalPayment.objects.values_list('status', flat=True).distinct()
155
149
  status_choices = [(status, status.title()) for status in statuses if status]
@@ -207,11 +207,15 @@ class PaymentStatsView(
207
207
  }
208
208
 
209
209
  # Convert seconds to human readable format
210
- for key in metrics:
211
- if metrics[key] > 0:
212
- metrics[f"{key}_formatted"] = self._format_duration(metrics[key])
210
+ formatted_metrics = {}
211
+ for key, value in metrics.items():
212
+ if value > 0:
213
+ formatted_metrics[f"{key}_formatted"] = self._format_duration(value)
213
214
  else:
214
- metrics[f"{key}_formatted"] = "N/A"
215
+ formatted_metrics[f"{key}_formatted"] = "N/A"
216
+
217
+ # Add formatted metrics to the original metrics
218
+ metrics.update(formatted_metrics)
215
219
 
216
220
  return metrics
217
221
 
@@ -3,7 +3,7 @@ Webhook processing views with signature validation.
3
3
  """
4
4
 
5
5
  import json
6
- import logging
6
+ from django_cfg.modules.django_logger import get_logger
7
7
  from typing import Dict, Any
8
8
 
9
9
  from django.http import JsonResponse, HttpResponse
@@ -20,7 +20,7 @@ from ..tasks.webhook_processing import process_webhook_with_fallback
20
20
  from ..services.security.webhook_validator import webhook_validator
21
21
  from ..services.security.error_handler import error_handler, SecurityError, ValidationError
22
22
 
23
- logger = logging.getLogger(__name__)
23
+ logger = get_logger("webhook_views")
24
24
 
25
25
 
26
26
  @csrf_exempt
@@ -19,7 +19,7 @@ from .views import (
19
19
  UserAPIKeyViewSet, APIKeyViewSet,
20
20
 
21
21
  # Currency ViewSets
22
- CurrencyViewSet, CurrencyNetworkViewSet,
22
+ CurrencyViewSet, NetworkViewSet, ProviderCurrencyViewSet,
23
23
 
24
24
  # Tariff ViewSets
25
25
  TariffViewSet, TariffEndpointGroupViewSet,
@@ -49,7 +49,8 @@ class PaymentSystemRouter:
49
49
 
50
50
  # Currency and pricing
51
51
  self.router.register(r'currencies', CurrencyViewSet, basename='currency')
52
- self.router.register(r'currency-networks', CurrencyNetworkViewSet, basename='currency-network')
52
+ self.router.register(r'networks', NetworkViewSet, basename='network')
53
+ self.router.register(r'provider-currencies', ProviderCurrencyViewSet, basename='provider-currency')
53
54
  self.router.register(r'tariffs', TariffViewSet, basename='tariff')
54
55
  self.router.register(r'tariff-groups', TariffEndpointGroupViewSet, basename='tariff-group')
55
56
 
@@ -18,6 +18,4 @@ urlpatterns = [
18
18
  # RESTful API endpoints using ViewSets
19
19
  path('api/', include(router.urls)),
20
20
 
21
- # Dashboard view
22
- path('dashboard/', views.dashboard_view, name='dashboard'),
23
21
  ]
@@ -0,0 +1,14 @@
1
+ """
2
+ URLs for Django CFG Tasks app.
3
+
4
+ Provides RESTful endpoints for task queue management and monitoring using ViewSets and routers.
5
+ """
6
+
7
+ from django.urls import path
8
+ from . import views
9
+
10
+ urlpatterns = [
11
+
12
+ # Dashboard view
13
+ path('dashboard/', views.dashboard_view, name='dashboard'),
14
+ ]
django_cfg/apps/urls.py CHANGED
@@ -46,14 +46,14 @@ def get_django_cfg_urlpatterns() -> List[URLPattern]:
46
46
 
47
47
  # Tasks app - enabled when knowbase or agents are enabled
48
48
  if base_module.should_enable_tasks():
49
- patterns.append(path('tasks/', include('django_cfg.apps.tasks.urls')))
49
+ patterns.append(path('admin/django_cfg_tasks/admin/', include('django_cfg.apps.tasks.urls_admin')))
50
50
 
51
51
  # Maintenance app - multi-site maintenance mode with Cloudflare
52
- if base_module.is_maintenance_enabled():
53
- patterns.append(path('maintenance/', include('django_cfg.apps.maintenance.urls')))
52
+ # if base_module.is_maintenance_enabled():
53
+ # patterns.append(path('admin/django_cfg_maintenance/', include('django_cfg.apps.maintenance.urls_admin')))
54
54
 
55
55
  if base_module.is_payments_enabled():
56
- patterns.append(path('payments/admin/', include('django_cfg.apps.payments.urls_templates')))
56
+ patterns.append(path('admin/django_cfg_payments/admin/', include('django_cfg.apps.payments.urls_admin')))
57
57
 
58
58
  except Exception:
59
59
  # Fallback: include all URLs if config is not available
django_cfg/core/config.py CHANGED
@@ -11,6 +11,7 @@ Following CRITICAL_REQUIREMENTS.md:
11
11
  from typing import Dict, List, Optional, Any, Union
12
12
  from pathlib import Path
13
13
  from pydantic import BaseModel, Field, field_validator, model_validator, PrivateAttr
14
+ from enum import Enum
14
15
  import os
15
16
  from pathlib import Path
16
17
  from urllib.parse import urlparse
@@ -66,6 +67,18 @@ DEFAULT_APPS = [
66
67
  ]
67
68
 
68
69
 
70
+ class EnvironmentMode(str, Enum):
71
+ """Environment mode enumeration."""
72
+ DEVELOPMENT = "development"
73
+ PRODUCTION = "production"
74
+ TEST = "test"
75
+
76
+ @classmethod
77
+ def from_debug(cls, debug: bool) -> "EnvironmentMode":
78
+ """Get environment mode from debug flag."""
79
+ return cls.DEVELOPMENT if debug else cls.PRODUCTION
80
+
81
+
69
82
  class DjangoConfig(BaseModel):
70
83
  """
71
84
  Base configuration class for Django projects.
@@ -105,6 +118,12 @@ class DjangoConfig(BaseModel):
105
118
  "str_strip_whitespace": True,
106
119
  }
107
120
 
121
+ # === Environment Configuration ===
122
+ env_mode: EnvironmentMode = Field(
123
+ default=EnvironmentMode.PRODUCTION,
124
+ description="Environment mode: development, production, or test",
125
+ )
126
+
108
127
  # === Project Information ===
109
128
  project_name: str = Field(
110
129
  ...,
@@ -401,6 +420,22 @@ class DjangoConfig(BaseModel):
401
420
 
402
421
  return self
403
422
 
423
+ # === Environment Mode Properties ===
424
+ @property
425
+ def is_development(self) -> bool:
426
+ """Check if running in development mode."""
427
+ return self.env_mode == EnvironmentMode.DEVELOPMENT
428
+
429
+ @property
430
+ def is_production(self) -> bool:
431
+ """Check if running in production mode."""
432
+ return self.env_mode == EnvironmentMode.PRODUCTION
433
+
434
+ @property
435
+ def is_test(self) -> bool:
436
+ """Check if running in test mode."""
437
+ return self.env_mode == EnvironmentMode.TEST
438
+
404
439
  def _detect_environment(self) -> None:
405
440
  """Detect current environment from various sources."""
406
441
  from django_cfg.core.environment import EnvironmentDetector
@@ -457,7 +457,6 @@ class PaymentsConfig(BaseModel):
457
457
  # Helper function for easy provider configuration
458
458
  def create_nowpayments_config(
459
459
  api_key: str,
460
- sandbox: bool = True,
461
460
  ipn_secret: Optional[str] = None,
462
461
  **kwargs
463
462
  ) -> NowPaymentsConfig:
@@ -465,7 +464,6 @@ def create_nowpayments_config(
465
464
  return NowPaymentsConfig(
466
465
  name="nowpayments",
467
466
  api_key=SecretStr(api_key),
468
- sandbox=sandbox,
469
467
  ipn_secret=SecretStr(ipn_secret) if ipn_secret else None,
470
468
  **kwargs
471
469
  )
@@ -489,16 +487,14 @@ def create_stripe_config(
489
487
  api_key: str,
490
488
  publishable_key: Optional[str] = None,
491
489
  webhook_endpoint_secret: Optional[str] = None,
492
- sandbox: bool = True,
493
490
  **kwargs
494
491
  ) -> StripeConfig:
495
- """Helper to create Stripe configuration."""
492
+ """Helper to create Stripe configuration with automatic sandbox detection."""
496
493
  return StripeConfig(
497
494
  name="stripe",
498
495
  api_key=SecretStr(api_key),
499
496
  publishable_key=publishable_key,
500
497
  webhook_endpoint_secret=SecretStr(webhook_endpoint_secret) if webhook_endpoint_secret else None,
501
- sandbox=sandbox,
502
498
  **kwargs
503
499
  )
504
500
 
@@ -506,13 +502,12 @@ def create_stripe_config(
506
502
  def create_cryptomus_config(
507
503
  api_key: str,
508
504
  merchant_uuid: str,
509
- sandbox: bool = True,
510
505
  callback_url: Optional[str] = None,
511
506
  success_url: Optional[str] = None,
512
507
  fail_url: Optional[str] = None,
513
508
  **kwargs
514
509
  ):
515
- """Helper to create Cryptomus configuration."""
510
+ """Helper to create Cryptomus configuration with automatic sandbox detection."""
516
511
  # Import here to avoid circular imports
517
512
  from django_cfg.apps.payments.config.providers import CryptomusConfig
518
513
 
@@ -520,7 +515,6 @@ def create_cryptomus_config(
520
515
  name="cryptomus",
521
516
  api_key=SecretStr(api_key),
522
517
  merchant_uuid=merchant_uuid,
523
- sandbox=sandbox,
524
518
  callback_url=callback_url,
525
519
  success_url=success_url,
526
520
  fail_url=fail_url,
@@ -11,9 +11,6 @@ from .core import (
11
11
  Rate,
12
12
  ConversionRequest,
13
13
  ConversionResult,
14
- SupportedCurrencies,
15
- YFinanceCurrencies,
16
- CoinGeckoCurrencies,
17
14
  CurrencyError,
18
15
  CurrencyNotFoundError,
19
16
  RateFetchError,
@@ -25,7 +22,7 @@ from .core import (
25
22
  from .utils import CacheManager
26
23
 
27
24
  # Clients
28
- from .clients import YFinanceClient, CoinGeckoClient
25
+ from .clients import YahooFinanceClient, CoinPaprikaClient
29
26
 
30
27
  # Database tools
31
28
  from .database import (
@@ -35,6 +32,17 @@ from .database import (
35
32
  load_currencies_to_database_format
36
33
  )
37
34
 
35
+ # Shared global converter instance for caching efficiency
36
+ _global_converter = None
37
+
38
+ def _get_converter() -> CurrencyConverter:
39
+ """Get or create shared converter instance."""
40
+ global _global_converter
41
+ if _global_converter is None:
42
+ _global_converter = CurrencyConverter(cache_ttl=3600) # 1 hour cache
43
+ return _global_converter
44
+
45
+
38
46
  # Simple public API
39
47
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> float:
40
48
  """
@@ -48,7 +56,7 @@ def convert_currency(amount: float, from_currency: str, to_currency: str) -> flo
48
56
  Returns:
49
57
  Converted amount
50
58
  """
51
- converter = CurrencyConverter()
59
+ converter = _get_converter()
52
60
  result = converter.convert(amount, from_currency, to_currency)
53
61
  return result.result
54
62
 
@@ -64,7 +72,7 @@ def get_exchange_rate(base: str, quote: str) -> float:
64
72
  Returns:
65
73
  Exchange rate
66
74
  """
67
- converter = CurrencyConverter()
75
+ converter = _get_converter()
68
76
  result = converter.convert(1.0, base, quote)
69
77
  return result.rate.rate
70
78
 
@@ -75,9 +83,6 @@ __all__ = [
75
83
  "Rate",
76
84
  "ConversionRequest",
77
85
  "ConversionResult",
78
- "SupportedCurrencies",
79
- "YFinanceCurrencies",
80
- "CoinGeckoCurrencies",
81
86
 
82
87
  # Exceptions
83
88
  "CurrencyError",
@@ -90,8 +95,8 @@ __all__ = [
90
95
  "CacheManager",
91
96
 
92
97
  # Clients
93
- "YFinanceClient",
94
- "CoinGeckoClient",
98
+ "YahooFinanceClient",
99
+ "CoinPaprikaClient",
95
100
 
96
101
  # Database tools
97
102
  "CurrencyDatabaseLoader",
@@ -2,10 +2,10 @@
2
2
  Currency data clients for fetching rates from external APIs.
3
3
  """
4
4
 
5
- from .yfinance_client import YFinanceClient
6
- from .coingecko_client import CoinGeckoClient
5
+ from .yahoo_client import YahooFinanceClient
6
+ from .coinpaprika_client import CoinPaprikaClient
7
7
 
8
8
  __all__ = [
9
- 'YFinanceClient',
10
- 'CoinGeckoClient'
9
+ 'YahooFinanceClient',
10
+ 'CoinPaprikaClient'
11
11
  ]