django-cfg 1.2.23__py3-none-any.whl → 1.2.27__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
- django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
- django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
- django_cfg/apps/payments/config/__init__.py +15 -37
- django_cfg/apps/payments/config/module.py +30 -122
- django_cfg/apps/payments/config/providers.py +28 -16
- django_cfg/apps/payments/config/settings.py +53 -93
- django_cfg/apps/payments/config/utils.py +10 -156
- django_cfg/apps/payments/management/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/README.md +178 -0
- django_cfg/apps/payments/management/commands/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
- django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
- django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
- django_cfg/apps/payments/managers/currency_manager.py +65 -14
- django_cfg/apps/payments/middleware/api_access.py +33 -0
- django_cfg/apps/payments/migrations/0001_initial.py +94 -1
- django_cfg/apps/payments/models/payments.py +110 -0
- django_cfg/apps/payments/services/__init__.py +7 -1
- django_cfg/apps/payments/services/core/balance_service.py +14 -16
- django_cfg/apps/payments/services/core/fallback_service.py +432 -0
- django_cfg/apps/payments/services/core/payment_service.py +212 -29
- django_cfg/apps/payments/services/core/subscription_service.py +15 -17
- django_cfg/apps/payments/services/internal_types.py +31 -0
- django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
- django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
- django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
- django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -0
- django_cfg/apps/payments/services/security/__init__.py +34 -0
- django_cfg/apps/payments/services/security/error_handler.py +637 -0
- django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
- django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
- django_cfg/apps/payments/signals/api_key_signals.py +10 -0
- django_cfg/apps/payments/signals/payment_signals.py +3 -2
- django_cfg/apps/payments/tasks/__init__.py +12 -0
- django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
- django_cfg/apps/payments/utils/__init__.py +7 -4
- django_cfg/apps/payments/utils/billing_utils.py +342 -0
- django_cfg/apps/payments/utils/config_utils.py +2 -0
- django_cfg/apps/payments/views/payment_views.py +40 -2
- django_cfg/apps/payments/views/webhook_views.py +266 -0
- django_cfg/apps/payments/viewsets.py +65 -0
- django_cfg/cli/README.md +2 -2
- django_cfg/cli/commands/create_project.py +1 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/main.py +1 -1
- django_cfg/cli/utils.py +5 -5
- django_cfg/core/config.py +18 -4
- django_cfg/models/payments.py +547 -0
- django_cfg/models/tasks.py +51 -2
- django_cfg/modules/base.py +11 -5
- django_cfg/modules/django_currency/README.md +104 -269
- django_cfg/modules/django_currency/__init__.py +99 -41
- django_cfg/modules/django_currency/clients/__init__.py +11 -0
- django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
- django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
- django_cfg/modules/django_currency/core/__init__.py +42 -0
- django_cfg/modules/django_currency/core/converter.py +169 -0
- django_cfg/modules/django_currency/core/exceptions.py +28 -0
- django_cfg/modules/django_currency/core/models.py +54 -0
- django_cfg/modules/django_currency/database/__init__.py +25 -0
- django_cfg/modules/django_currency/database/database_loader.py +507 -0
- django_cfg/modules/django_currency/utils/__init__.py +9 -0
- django_cfg/modules/django_currency/utils/cache.py +92 -0
- django_cfg/registry/core.py +10 -0
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/METADATA +10 -6
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/RECORD +77 -51
- django_cfg/apps/agents/examples/__init__.py +0 -3
- django_cfg/apps/agents/examples/simple_example.py +0 -161
- django_cfg/apps/knowbase/examples/__init__.py +0 -3
- django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
- django_cfg/modules/django_currency/cache.py +0 -430
- django_cfg/modules/django_currency/converter.py +0 -324
- django_cfg/modules/django_currency/service.py +0 -277
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.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/
|
509
|
+
- **GitHub**: https://github.com/markolofsen/django-cfg
|
510
510
|
- **PyPI**: https://pypi.org/project/django-cfg/
|
511
|
-
- **Examples**: https://github.com/
|
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/
|
260
|
+
For more information, visit: [https://github.com/markolofsen/django-cfg](https://github.com/markolofsen/django-cfg)
|
261
261
|
|
262
262
|
## 📄 License
|
263
263
|
|
django_cfg/cli/commands/info.py
CHANGED
@@ -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/
|
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
|
52
|
-
search_paths.append(package_path / "
|
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" / "
|
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" / "
|
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" / "
|
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
|
-
|
162
|
-
|
163
|
-
|
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.
|
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
|
|