django-cfg 1.2.23__py3-none-any.whl → 1.2.25__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
  3. django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
  4. django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
  5. django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
  6. django_cfg/apps/payments/config/__init__.py +15 -37
  7. django_cfg/apps/payments/config/module.py +30 -122
  8. django_cfg/apps/payments/config/providers.py +22 -0
  9. django_cfg/apps/payments/config/settings.py +53 -93
  10. django_cfg/apps/payments/config/utils.py +10 -156
  11. django_cfg/apps/payments/management/__init__.py +3 -0
  12. django_cfg/apps/payments/management/commands/README.md +178 -0
  13. django_cfg/apps/payments/management/commands/__init__.py +3 -0
  14. django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
  15. django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
  16. django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
  17. django_cfg/apps/payments/managers/currency_manager.py +65 -14
  18. django_cfg/apps/payments/middleware/api_access.py +33 -0
  19. django_cfg/apps/payments/migrations/0001_initial.py +94 -1
  20. django_cfg/apps/payments/models/payments.py +110 -0
  21. django_cfg/apps/payments/services/__init__.py +7 -1
  22. django_cfg/apps/payments/services/core/balance_service.py +14 -16
  23. django_cfg/apps/payments/services/core/fallback_service.py +432 -0
  24. django_cfg/apps/payments/services/core/payment_service.py +212 -29
  25. django_cfg/apps/payments/services/core/subscription_service.py +15 -17
  26. django_cfg/apps/payments/services/internal_types.py +31 -0
  27. django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
  28. django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
  29. django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
  30. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  31. django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
  32. django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
  33. django_cfg/apps/payments/services/providers/registry.py +4 -0
  34. django_cfg/apps/payments/services/security/__init__.py +34 -0
  35. django_cfg/apps/payments/services/security/error_handler.py +637 -0
  36. django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
  37. django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
  38. django_cfg/apps/payments/signals/api_key_signals.py +10 -0
  39. django_cfg/apps/payments/signals/payment_signals.py +3 -2
  40. django_cfg/apps/payments/tasks/__init__.py +12 -0
  41. django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
  42. django_cfg/apps/payments/utils/__init__.py +7 -4
  43. django_cfg/apps/payments/utils/billing_utils.py +342 -0
  44. django_cfg/apps/payments/utils/config_utils.py +2 -0
  45. django_cfg/apps/payments/views/payment_views.py +40 -2
  46. django_cfg/apps/payments/views/webhook_views.py +266 -0
  47. django_cfg/apps/payments/viewsets.py +65 -0
  48. django_cfg/cli/README.md +2 -2
  49. django_cfg/cli/commands/create_project.py +1 -1
  50. django_cfg/cli/commands/info.py +1 -1
  51. django_cfg/cli/main.py +1 -1
  52. django_cfg/cli/utils.py +5 -5
  53. django_cfg/core/config.py +18 -4
  54. django_cfg/models/payments.py +546 -0
  55. django_cfg/models/tasks.py +51 -2
  56. django_cfg/modules/base.py +11 -5
  57. django_cfg/modules/django_currency/README.md +104 -269
  58. django_cfg/modules/django_currency/__init__.py +99 -41
  59. django_cfg/modules/django_currency/clients/__init__.py +11 -0
  60. django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
  61. django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
  62. django_cfg/modules/django_currency/core/__init__.py +42 -0
  63. django_cfg/modules/django_currency/core/converter.py +169 -0
  64. django_cfg/modules/django_currency/core/exceptions.py +28 -0
  65. django_cfg/modules/django_currency/core/models.py +54 -0
  66. django_cfg/modules/django_currency/database/__init__.py +25 -0
  67. django_cfg/modules/django_currency/database/database_loader.py +507 -0
  68. django_cfg/modules/django_currency/utils/__init__.py +9 -0
  69. django_cfg/modules/django_currency/utils/cache.py +92 -0
  70. django_cfg/registry/core.py +10 -0
  71. django_cfg/template_archive/__init__.py +0 -0
  72. django_cfg/template_archive/django_sample.zip +0 -0
  73. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/METADATA +10 -6
  74. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/RECORD +77 -51
  75. django_cfg/apps/agents/examples/__init__.py +0 -3
  76. django_cfg/apps/agents/examples/simple_example.py +0 -161
  77. django_cfg/apps/knowbase/examples/__init__.py +0 -3
  78. django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
  79. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
  80. django_cfg/modules/django_currency/cache.py +0 -430
  81. django_cfg/modules/django_currency/converter.py +0 -324
  82. django_cfg/modules/django_currency/service.py +0 -277
  83. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/WHEEL +0 -0
  84. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/entry_points.txt +0 -0
  85. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,266 @@
1
+ """
2
+ Webhook processing views with signature validation.
3
+ """
4
+
5
+ import json
6
+ import logging
7
+ from typing import Dict, Any
8
+
9
+ from django.http import JsonResponse, HttpResponse
10
+ from django.views.decorators.csrf import csrf_exempt
11
+ from django.views.decorators.http import require_http_methods
12
+ from django.utils.decorators import method_decorator
13
+ from rest_framework.decorators import api_view, permission_classes
14
+ from rest_framework.permissions import AllowAny
15
+ from rest_framework.response import Response
16
+ from rest_framework import status
17
+
18
+ from ..services.core.payment_service import PaymentService
19
+ from ..tasks.webhook_processing import process_webhook_with_fallback
20
+ from ..services.security.webhook_validator import webhook_validator
21
+ from ..services.security.error_handler import error_handler, SecurityError, ValidationError
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ @csrf_exempt
27
+ @require_http_methods(["POST"])
28
+ def webhook_handler(request, provider: str):
29
+ """
30
+ Main webhook handler with signature validation.
31
+
32
+ Accepts webhooks from payment providers and processes them
33
+ with proper validation and fallback mechanisms.
34
+ """
35
+ try:
36
+ # Parse webhook data
37
+ webhook_data = json.loads(request.body.decode('utf-8'))
38
+
39
+ # Extract request headers
40
+ request_headers = {
41
+ key: value for key, value in request.META.items()
42
+ if key.startswith('HTTP_')
43
+ }
44
+
45
+ # Generate idempotency key for deduplication
46
+ idempotency_key = _generate_idempotency_key(provider, webhook_data, request_headers)
47
+
48
+ logger.info(f"📥 Received webhook from {provider}, key: {idempotency_key}")
49
+
50
+ # Validate webhook with enhanced security
51
+ is_valid, validation_error = webhook_validator.validate_webhook(
52
+ provider=provider,
53
+ webhook_data=webhook_data,
54
+ request_headers=request_headers,
55
+ raw_body=request.body
56
+ )
57
+
58
+ if not is_valid:
59
+ security_error = SecurityError(
60
+ f"Webhook validation failed: {validation_error}",
61
+ details={'provider': provider, 'validation_error': validation_error}
62
+ )
63
+ error_handler.handle_error(security_error, {
64
+ 'provider': provider,
65
+ 'webhook_data_keys': list(webhook_data.keys()),
66
+ 'headers_count': len(request_headers)
67
+ }, request)
68
+
69
+ return JsonResponse(
70
+ {'error': 'Webhook validation failed', 'code': 'INVALID_WEBHOOK'},
71
+ status=403
72
+ )
73
+
74
+ # Process webhook (async with fallback to sync)
75
+ result = process_webhook_with_fallback(
76
+ provider=provider,
77
+ webhook_data=webhook_data,
78
+ idempotency_key=idempotency_key,
79
+ request_headers=request_headers
80
+ )
81
+
82
+ if result.get('success'):
83
+ logger.info(f"✅ Webhook processed successfully: {idempotency_key}")
84
+ return JsonResponse({
85
+ 'status': 'success',
86
+ 'idempotency_key': idempotency_key,
87
+ 'processing_mode': result.get('mode', 'unknown')
88
+ })
89
+ else:
90
+ logger.error(f"❌ Webhook processing failed: {result.get('error')}")
91
+ return JsonResponse({
92
+ 'status': 'error',
93
+ 'error': result.get('error', 'Processing failed'),
94
+ 'idempotency_key': idempotency_key
95
+ }, status=400)
96
+
97
+ except json.JSONDecodeError as e:
98
+ validation_error = ValidationError(
99
+ f"Invalid JSON in webhook from {provider}",
100
+ details={'provider': provider, 'json_error': str(e)}
101
+ )
102
+ error_result = error_handler.handle_error(validation_error, {
103
+ 'provider': provider,
104
+ 'raw_body_length': len(request.body) if request.body else 0
105
+ }, request)
106
+
107
+ return JsonResponse({
108
+ 'error': 'Invalid JSON',
109
+ 'code': validation_error.error_code
110
+ }, status=400)
111
+
112
+ except Exception as e:
113
+ # Handle unexpected errors with centralized error handler
114
+ error_result = error_handler.handle_error(e, {
115
+ 'provider': provider,
116
+ 'operation': 'webhook_processing',
117
+ 'webhook_data_available': 'webhook_data' in locals()
118
+ }, request)
119
+
120
+ return JsonResponse({
121
+ 'error': 'Internal server error',
122
+ 'code': error_result.error.error_code
123
+ }, status=500)
124
+
125
+
126
+ @api_view(['POST'])
127
+ @permission_classes([AllowAny])
128
+ def webhook_test(request):
129
+ """
130
+ Test webhook endpoint for development.
131
+
132
+ Allows testing webhook processing without requiring
133
+ actual payment provider signatures.
134
+ """
135
+ try:
136
+ provider = request.data.get('provider', 'test')
137
+ webhook_data = request.data.get('webhook_data', {})
138
+
139
+ # Add test marker
140
+ webhook_data['_test_webhook'] = True
141
+
142
+ # Generate test idempotency key
143
+ import uuid
144
+ idempotency_key = f"test_{uuid.uuid4().hex[:8]}"
145
+
146
+ logger.info(f"🧪 Processing test webhook: {provider}")
147
+
148
+ # Process with PaymentService directly (sync)
149
+ payment_service = PaymentService()
150
+ result = payment_service.process_webhook(
151
+ provider=provider,
152
+ webhook_data=webhook_data,
153
+ request_headers={'HTTP_X_TEST': 'true'}
154
+ )
155
+
156
+ return Response({
157
+ 'status': 'success',
158
+ 'test_mode': True,
159
+ 'provider': provider,
160
+ 'idempotency_key': idempotency_key,
161
+ 'result': result.dict() if hasattr(result, 'dict') else result
162
+ })
163
+
164
+ except Exception as e:
165
+ logger.error(f"❌ Test webhook error: {e}")
166
+ return Response({
167
+ 'status': 'error',
168
+ 'error': str(e),
169
+ 'test_mode': True
170
+ }, status=status.HTTP_400_BAD_REQUEST)
171
+
172
+
173
+ def _validate_webhook_signature(provider: str, webhook_data: Dict[str, Any],
174
+ request_headers: Dict[str, str]) -> bool:
175
+ """
176
+ Validate webhook signature based on provider.
177
+
178
+ Each provider has different signature validation methods.
179
+ """
180
+ try:
181
+ if provider == 'nowpayments':
182
+ return _validate_nowpayments_signature(webhook_data, request_headers)
183
+ elif provider == 'cryptapi':
184
+ return _validate_cryptapi_signature(webhook_data, request_headers)
185
+ elif provider == 'test':
186
+ return True # Allow test webhooks
187
+ else:
188
+ logger.warning(f"Unknown provider for signature validation: {provider}")
189
+ return False
190
+
191
+ except Exception as e:
192
+ logger.error(f"Signature validation error for {provider}: {e}")
193
+ return False
194
+
195
+
196
+ def _validate_nowpayments_signature(webhook_data: Dict[str, Any],
197
+ request_headers: Dict[str, str]) -> bool:
198
+ """Validate NowPayments webhook signature."""
199
+ import hmac
200
+ import hashlib
201
+ from ..utils.config_utils import get_payments_config
202
+
203
+ # Get IPN secret from config
204
+ config = get_payments_config()
205
+ if not config or not hasattr(config, 'providers') or 'nowpayments' not in config.providers:
206
+ logger.warning("NowPayments IPN secret not configured, skipping validation")
207
+ return True # Allow if not configured (development mode)
208
+
209
+ nowpayments_config = config.providers['nowpayments']
210
+ ipn_secret = getattr(nowpayments_config, 'ipn_secret', None)
211
+
212
+ if not ipn_secret:
213
+ logger.warning("NowPayments IPN secret not configured, skipping validation")
214
+ return True
215
+
216
+ # Get signature from headers
217
+ signature = request_headers.get('HTTP_X_NOWPAYMENTS_SIG')
218
+ if not signature:
219
+ logger.warning("No NowPayments signature found in headers")
220
+ return False
221
+
222
+ # Calculate expected signature
223
+ payload = json.dumps(webhook_data, separators=(',', ':'), sort_keys=True)
224
+ expected_signature = hmac.new(
225
+ ipn_secret.encode(),
226
+ payload.encode(),
227
+ hashlib.sha512
228
+ ).hexdigest()
229
+
230
+ return hmac.compare_digest(signature, expected_signature)
231
+
232
+
233
+ def _validate_cryptapi_signature(webhook_data: Dict[str, Any],
234
+ request_headers: Dict[str, str]) -> bool:
235
+ """Validate CryptAPI webhook signature."""
236
+ # CryptAPI uses different validation method
237
+ # For now, implement basic validation
238
+
239
+ # Check if required fields are present
240
+ required_fields = ['address_in', 'address_out', 'txid_in', 'value_coin', 'coin', 'confirmations']
241
+ for field in required_fields:
242
+ if field not in webhook_data:
243
+ logger.warning(f"Missing required field in CryptAPI webhook: {field}")
244
+ return False
245
+
246
+ return True
247
+
248
+
249
+ def _generate_idempotency_key(provider: str, webhook_data: Dict[str, Any],
250
+ request_headers: Dict[str, str]) -> str:
251
+ """Generate idempotency key for webhook deduplication."""
252
+ import hashlib
253
+
254
+ # Use provider + payment ID + timestamp for uniqueness
255
+ payment_id = (
256
+ webhook_data.get('payment_id') or
257
+ webhook_data.get('order_id') or
258
+ webhook_data.get('id') or
259
+ 'unknown'
260
+ )
261
+
262
+ timestamp = webhook_data.get('created_at') or webhook_data.get('timestamp')
263
+
264
+ # Create hash from key components
265
+ key_data = f"{provider}:{payment_id}:{timestamp}"
266
+ return hashlib.md5(key_data.encode()).hexdigest()[:16]
@@ -0,0 +1,65 @@
1
+ """
2
+ Payment system ViewSets router for easy integration.
3
+ """
4
+
5
+ from rest_framework.routers import DefaultRouter
6
+ from rest_framework_nested import routers
7
+
8
+ from .views import (
9
+ # Balance ViewSets
10
+ UserBalanceViewSet, TransactionViewSet,
11
+
12
+ # Payment ViewSets
13
+ UserPaymentViewSet, UniversalPaymentViewSet,
14
+
15
+ # Subscription ViewSets
16
+ UserSubscriptionViewSet, SubscriptionViewSet, EndpointGroupViewSet,
17
+
18
+ # API Key ViewSets
19
+ UserAPIKeyViewSet, APIKeyViewSet,
20
+
21
+ # Currency ViewSets
22
+ CurrencyViewSet, CurrencyNetworkViewSet,
23
+
24
+ # Tariff ViewSets
25
+ TariffViewSet, TariffEndpointGroupViewSet,
26
+ )
27
+
28
+
29
+ class PaymentSystemRouter:
30
+ """Universal router with all payment endpoints"""
31
+
32
+ def __init__(self):
33
+ self.router = DefaultRouter()
34
+ self._setup_main_routes()
35
+
36
+ def _setup_main_routes(self):
37
+ """Setup main resource routes"""
38
+ # Core payment resources
39
+ self.router.register(r'payments', UniversalPaymentViewSet, basename='payment')
40
+ self.router.register(r'balances', UserBalanceViewSet, basename='balance')
41
+ self.router.register(r'transactions', TransactionViewSet, basename='transaction')
42
+
43
+ # Subscription management
44
+ self.router.register(r'subscriptions', SubscriptionViewSet, basename='subscription')
45
+ self.router.register(r'endpoint-groups', EndpointGroupViewSet, basename='endpoint-group')
46
+
47
+ # API key management
48
+ self.router.register(r'api-keys', APIKeyViewSet, basename='api-key')
49
+
50
+ # Currency and pricing
51
+ self.router.register(r'currencies', CurrencyViewSet, basename='currency')
52
+ self.router.register(r'currency-networks', CurrencyNetworkViewSet, basename='currency-network')
53
+ self.router.register(r'tariffs', TariffViewSet, basename='tariff')
54
+ self.router.register(r'tariff-groups', TariffEndpointGroupViewSet, basename='tariff-group')
55
+
56
+ @property
57
+ def urls(self):
58
+ """Get all URLs"""
59
+ return self.router.urls
60
+
61
+
62
+ # Create default router instance
63
+ payment_router = PaymentSystemRouter()
64
+
65
+ __all__ = ['PaymentSystemRouter', 'payment_router']
django_cfg/cli/README.md CHANGED
@@ -506,9 +506,9 @@ print(f"Django installed: {deps['django']}")
506
506
  ## 📚 Documentation
507
507
 
508
508
  - **Django CFG**: https://djangocfg.com
509
- - **GitHub**: https://github.com/reformsai/django-cfg
509
+ - **GitHub**: https://github.com/markolofsen/django-cfg
510
510
  - **PyPI**: https://pypi.org/project/django-cfg/
511
- - **Examples**: https://github.com/reformsai/django-cfg/tree/main/examples
511
+ - **Examples**: https://github.com/markolofsen/django-cfg/tree/main/examples
512
512
 
513
513
  ## 📄 License
514
514
 
@@ -257,7 +257,7 @@ python manage.py translate_content
257
257
  ## 🤝 Contributing
258
258
 
259
259
  This project uses **django-cfg** for configuration management.
260
- For more information, visit: [https://github.com/reformsai/django-cfg](https://github.com/reformsai/django-cfg)
260
+ For more information, visit: [https://github.com/markolofsen/django-cfg](https://github.com/markolofsen/django-cfg)
261
261
 
262
262
  ## 📄 License
263
263
 
@@ -120,7 +120,7 @@ def info(verbose: bool):
120
120
  click.echo(" pip install twilio sendgrid django-unfold")
121
121
  click.echo()
122
122
  click.echo("📚 Documentation: https://djangocfg.com")
123
- click.echo("🐙 GitHub: https://github.com/reformsai/django-cfg")
123
+ click.echo("🐙 GitHub: https://github.com/markolofsen/django-cfg")
124
124
 
125
125
  # Warnings for missing critical dependencies
126
126
  missing_critical = [dep for dep in ["django", "pydantic"] if not deps.get(dep, False)]
django_cfg/cli/main.py CHANGED
@@ -11,7 +11,7 @@ from typing import Optional
11
11
  from .commands.create_project import create_project
12
12
  from .commands.info import info
13
13
  from .utils import get_package_info
14
- from ..version_check import check_python_version
14
+ from ..utils.version_check import check_python_version
15
15
 
16
16
 
17
17
  @click.group(name="django-cfg")
django_cfg/cli/utils.py CHANGED
@@ -48,15 +48,15 @@ def find_template_archive() -> Optional[Path]:
48
48
  import django_cfg
49
49
  package_path = Path(django_cfg.__file__).parent
50
50
 
51
- # Method 1: Package archive directory
52
- search_paths.append(package_path / "archive" / "django_sample.zip")
51
+ # Method 1: Package template_archive directory
52
+ search_paths.append(package_path / "template_archive" / "django_sample.zip")
53
53
 
54
54
  # Method 2: Site-packages shared data
55
55
  site_packages = Path(sysconfig.get_paths()["purelib"])
56
- search_paths.append(site_packages / "django_cfg" / "archive" / "django_sample.zip")
56
+ search_paths.append(site_packages / "django_cfg" / "template_archive" / "django_sample.zip")
57
57
 
58
58
  # Method 3: Development installation - src directory
59
- dev_path = package_path.parent.parent / "src" / "django_cfg" / "archive" / "django_sample.zip"
59
+ dev_path = package_path.parent.parent / "src" / "django_cfg" / "template_archive" / "django_sample.zip"
60
60
  search_paths.append(dev_path)
61
61
 
62
62
  except ImportError:
@@ -64,7 +64,7 @@ def find_template_archive() -> Optional[Path]:
64
64
 
65
65
  # Method 4: Relative to CLI files (development)
66
66
  cli_path = Path(__file__).parent.parent.parent.parent
67
- search_paths.append(cli_path / "src" / "django_cfg" / "archive" / "django_sample.zip")
67
+ search_paths.append(cli_path / "src" / "django_cfg" / "template_archive" / "django_sample.zip")
68
68
 
69
69
  # Return first existing path
70
70
  for path in search_paths:
django_cfg/core/config.py CHANGED
@@ -21,6 +21,7 @@ from django_cfg import (
21
21
  UnfoldConfig, DRFConfig, SpectacularConfig, LimitsConfig
22
22
  )
23
23
  from django_cfg.models.tasks import TaskConfig
24
+ from django_cfg.models.payments import PaymentsConfig
24
25
 
25
26
  # Default apps
26
27
  DEFAULT_APPS = [
@@ -158,9 +159,10 @@ class DjangoConfig(BaseModel):
158
159
  default=False,
159
160
  description="Enable django-cfg Maintenance application (multi-site maintenance mode with Cloudflare)",
160
161
  )
161
- enable_payments: bool = Field(
162
- default=False,
163
- description="Enable django-cfg Payments application (universal payment system, subscriptions, API keys, billing)",
162
+ # === Payment System Configuration ===
163
+ payments: Optional[PaymentsConfig] = Field(
164
+ default=None,
165
+ description="Universal payment system configuration (providers, subscriptions, API keys, billing)",
164
166
  )
165
167
 
166
168
  # === URLs ===
@@ -564,6 +566,14 @@ class DjangoConfig(BaseModel):
564
566
  if self.enable_knowbase or self.enable_agents:
565
567
  return True
566
568
 
569
+ # Check if payments module requires tasks
570
+ if self.payments and self.payments.should_enable_tasks():
571
+ return True
572
+
573
+ # Check if agents module requires tasks
574
+ if self.enable_agents:
575
+ return True
576
+
567
577
  return False
568
578
 
569
579
  def get_installed_apps(self) -> List[str]:
@@ -597,7 +607,7 @@ class DjangoConfig(BaseModel):
597
607
  apps.append("django_cfg.apps.agents")
598
608
  if self.enable_maintenance:
599
609
  apps.append("django_cfg.apps.maintenance")
600
- if self.enable_payments:
610
+ if self.payments and self.payments.enabled:
601
611
  apps.append("django_cfg.apps.payments")
602
612
 
603
613
  # Auto-enable tasks if needed
@@ -694,6 +704,10 @@ class DjangoConfig(BaseModel):
694
704
  if self.enable_accounts:
695
705
  middleware.append("django_cfg.middleware.UserActivityMiddleware")
696
706
 
707
+ # Add payments middleware if enabled
708
+ if self.payments and self.payments.enabled:
709
+ middleware.extend(self.payments.get_middleware_classes())
710
+
697
711
  # Add custom middleware
698
712
  middleware.extend(self.custom_middleware)
699
713