django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -10
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +172 -148
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -285
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +346 -467
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +40 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -146
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
- django_cfg/apps/payments/managers/__init__.py +0 -23
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -63
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -461
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -63
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -122
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -66
- django_cfg/core/integration.py +0 -160
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,228 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Utilities for middleware processing.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from typing import Optional, List
|
6
|
-
from django.http import HttpRequest
|
7
|
-
from django.conf import settings
|
8
|
-
|
9
|
-
|
10
|
-
def get_client_ip(request: HttpRequest) -> Optional[str]:
|
11
|
-
"""
|
12
|
-
Get client IP address from request.
|
13
|
-
Handles various proxy headers and configurations.
|
14
|
-
"""
|
15
|
-
|
16
|
-
# Check for forwarded headers first
|
17
|
-
forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
18
|
-
if forwarded_for:
|
19
|
-
# Take first IP in chain (original client)
|
20
|
-
return forwarded_for.split(',')[0].strip()
|
21
|
-
|
22
|
-
# Check for real IP header (common with nginx)
|
23
|
-
real_ip = request.META.get('HTTP_X_REAL_IP')
|
24
|
-
if real_ip:
|
25
|
-
return real_ip.strip()
|
26
|
-
|
27
|
-
# Check for Cloudflare header
|
28
|
-
cf_ip = request.META.get('HTTP_CF_CONNECTING_IP')
|
29
|
-
if cf_ip:
|
30
|
-
return cf_ip.strip()
|
31
|
-
|
32
|
-
# Fallback to remote address
|
33
|
-
remote_addr = request.META.get('REMOTE_ADDR')
|
34
|
-
if remote_addr:
|
35
|
-
return remote_addr.strip()
|
36
|
-
|
37
|
-
return None
|
38
|
-
|
39
|
-
|
40
|
-
def is_api_request(request: HttpRequest, api_prefixes: Optional[List[str]] = None) -> bool:
|
41
|
-
"""
|
42
|
-
Check if request is an API request based on path prefixes.
|
43
|
-
|
44
|
-
Args:
|
45
|
-
request: Django HTTP request
|
46
|
-
api_prefixes: List of API prefixes to check (defaults to settings)
|
47
|
-
"""
|
48
|
-
|
49
|
-
if api_prefixes is None:
|
50
|
-
api_prefixes = getattr(settings, 'PAYMENTS_API_PREFIXES', ['/api/'])
|
51
|
-
|
52
|
-
path = request.path
|
53
|
-
return any(path.startswith(prefix) for prefix in api_prefixes)
|
54
|
-
|
55
|
-
|
56
|
-
def extract_api_key(request: HttpRequest) -> Optional[str]:
|
57
|
-
"""
|
58
|
-
Extract API key from request headers or query parameters.
|
59
|
-
Supports multiple authentication methods.
|
60
|
-
|
61
|
-
Priority:
|
62
|
-
1. Authorization header (Bearer token)
|
63
|
-
2. X-API-Key header
|
64
|
-
3. Query parameter (less secure, for testing)
|
65
|
-
"""
|
66
|
-
|
67
|
-
# Method 1: Authorization header (Bearer token)
|
68
|
-
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
69
|
-
if auth_header.startswith('Bearer '):
|
70
|
-
return auth_header[7:] # Remove 'Bearer ' prefix
|
71
|
-
|
72
|
-
# Method 2: X-API-Key header
|
73
|
-
api_key_header = request.META.get('HTTP_X_API_KEY')
|
74
|
-
if api_key_header:
|
75
|
-
return api_key_header.strip()
|
76
|
-
|
77
|
-
# Method 3: Custom header variations
|
78
|
-
custom_headers = [
|
79
|
-
'HTTP_X_API_TOKEN',
|
80
|
-
'HTTP_APIKEY',
|
81
|
-
'HTTP_API_TOKEN',
|
82
|
-
]
|
83
|
-
|
84
|
-
for header in custom_headers:
|
85
|
-
value = request.META.get(header)
|
86
|
-
if value:
|
87
|
-
return value.strip()
|
88
|
-
|
89
|
-
# Method 4: Query parameter (less secure, mainly for testing)
|
90
|
-
if getattr(settings, 'PAYMENTS_ALLOW_API_KEY_IN_QUERY', False):
|
91
|
-
query_key = request.GET.get('api_key') or request.GET.get('apikey')
|
92
|
-
if query_key:
|
93
|
-
return query_key.strip()
|
94
|
-
|
95
|
-
return None
|
96
|
-
|
97
|
-
|
98
|
-
def is_exempt_path(request: HttpRequest, exempt_paths: Optional[List[str]] = None) -> bool:
|
99
|
-
"""
|
100
|
-
Check if request path is exempt from API key requirements.
|
101
|
-
|
102
|
-
Args:
|
103
|
-
request: Django HTTP request
|
104
|
-
exempt_paths: List of exempt path prefixes (defaults to settings)
|
105
|
-
"""
|
106
|
-
|
107
|
-
if exempt_paths is None:
|
108
|
-
exempt_paths = getattr(settings, 'PAYMENTS_EXEMPT_PATHS', [
|
109
|
-
'/admin/',
|
110
|
-
'/cfg/',
|
111
|
-
'/api/v1/api-key/validate/',
|
112
|
-
])
|
113
|
-
|
114
|
-
path = request.path
|
115
|
-
return any(path.startswith(exempt) for exempt in exempt_paths)
|
116
|
-
|
117
|
-
|
118
|
-
def get_request_metadata(request: HttpRequest) -> dict:
|
119
|
-
"""
|
120
|
-
Extract useful metadata from request for logging and analytics.
|
121
|
-
"""
|
122
|
-
|
123
|
-
return {
|
124
|
-
'method': request.method,
|
125
|
-
'path': request.path,
|
126
|
-
'query_string': request.META.get('QUERY_STRING', ''),
|
127
|
-
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
|
128
|
-
'referer': request.META.get('HTTP_REFERER', ''),
|
129
|
-
'ip_address': get_client_ip(request),
|
130
|
-
'content_type': request.META.get('CONTENT_TYPE', ''),
|
131
|
-
'content_length': request.META.get('CONTENT_LENGTH', 0),
|
132
|
-
'host': request.META.get('HTTP_HOST', ''),
|
133
|
-
'scheme': request.scheme,
|
134
|
-
'is_secure': request.is_secure(),
|
135
|
-
}
|
136
|
-
|
137
|
-
|
138
|
-
def should_track_request_body(request: HttpRequest, max_size: int = 10000) -> bool:
|
139
|
-
"""
|
140
|
-
Determine if request body should be tracked for analytics.
|
141
|
-
|
142
|
-
Args:
|
143
|
-
request: Django HTTP request
|
144
|
-
max_size: Maximum body size to track (bytes)
|
145
|
-
"""
|
146
|
-
|
147
|
-
# Check content length
|
148
|
-
content_length = request.META.get('CONTENT_LENGTH')
|
149
|
-
if content_length and int(content_length) > max_size:
|
150
|
-
return False
|
151
|
-
|
152
|
-
# Don't track file uploads
|
153
|
-
content_type = request.META.get('CONTENT_TYPE', '')
|
154
|
-
if 'multipart/form-data' in content_type:
|
155
|
-
return False
|
156
|
-
|
157
|
-
# Don't track binary content
|
158
|
-
if 'application/octet-stream' in content_type:
|
159
|
-
return False
|
160
|
-
|
161
|
-
# Don't track sensitive endpoints
|
162
|
-
sensitive_paths = getattr(settings, 'PAYMENTS_SENSITIVE_PATHS', [
|
163
|
-
'/api/v1/api-key/',
|
164
|
-
'/api/v1/payment/',
|
165
|
-
'/api/v1/subscription/',
|
166
|
-
])
|
167
|
-
|
168
|
-
path = request.path
|
169
|
-
if any(path.startswith(sensitive) for sensitive in sensitive_paths):
|
170
|
-
return False
|
171
|
-
|
172
|
-
return True
|
173
|
-
|
174
|
-
|
175
|
-
def should_track_response_body(response, max_size: int = 10000) -> bool:
|
176
|
-
"""
|
177
|
-
Determine if response body should be tracked for analytics.
|
178
|
-
|
179
|
-
Args:
|
180
|
-
response: Django HTTP response
|
181
|
-
max_size: Maximum body size to track (bytes)
|
182
|
-
"""
|
183
|
-
|
184
|
-
# Don't track large responses
|
185
|
-
if hasattr(response, 'content') and len(response.content) > max_size:
|
186
|
-
return False
|
187
|
-
|
188
|
-
# Only track successful JSON responses
|
189
|
-
if not (200 <= response.status_code < 300):
|
190
|
-
return False
|
191
|
-
|
192
|
-
# Check content type
|
193
|
-
content_type = response.get('Content-Type', '')
|
194
|
-
if 'application/json' not in content_type:
|
195
|
-
return False
|
196
|
-
|
197
|
-
return True
|
198
|
-
|
199
|
-
|
200
|
-
def format_error_response(error_code: str,
|
201
|
-
message: str,
|
202
|
-
status_code: int = 400,
|
203
|
-
additional_data: Optional[dict] = None) -> dict:
|
204
|
-
"""
|
205
|
-
Format standardized error response for middleware.
|
206
|
-
|
207
|
-
Args:
|
208
|
-
error_code: Machine-readable error code
|
209
|
-
message: Human-readable error message
|
210
|
-
status_code: HTTP status code
|
211
|
-
additional_data: Additional error data
|
212
|
-
"""
|
213
|
-
|
214
|
-
from django.utils import timezone
|
215
|
-
|
216
|
-
error_response = {
|
217
|
-
'error': {
|
218
|
-
'code': error_code,
|
219
|
-
'message': message,
|
220
|
-
'status_code': status_code,
|
221
|
-
'timestamp': timezone.now().isoformat(),
|
222
|
-
}
|
223
|
-
}
|
224
|
-
|
225
|
-
if additional_data:
|
226
|
-
error_response['error'].update(additional_data)
|
227
|
-
|
228
|
-
return error_response
|
@@ -1,94 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Validation utilities for payments module.
|
3
|
-
|
4
|
-
Basic validation functions for API keys and subscription access.
|
5
|
-
"""
|
6
|
-
|
7
|
-
from django_cfg.modules.django_logger import get_logger
|
8
|
-
from typing import Optional, Dict, Any
|
9
|
-
from django.contrib.auth import get_user_model
|
10
|
-
from django.utils import timezone
|
11
|
-
|
12
|
-
from ..models import APIKey, Subscription
|
13
|
-
|
14
|
-
User = get_user_model()
|
15
|
-
logger = get_logger("validation_utils")
|
16
|
-
|
17
|
-
|
18
|
-
def validate_api_key(api_key: str) -> bool:
|
19
|
-
"""
|
20
|
-
Validate API key.
|
21
|
-
|
22
|
-
Args:
|
23
|
-
api_key: API key to validate
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
True if valid, False otherwise
|
27
|
-
"""
|
28
|
-
try:
|
29
|
-
key = APIKey.objects.select_related('user').get(
|
30
|
-
key_value=api_key,
|
31
|
-
is_active=True
|
32
|
-
)
|
33
|
-
|
34
|
-
# Check if key is expired
|
35
|
-
if key.expires_at and key.expires_at < timezone.now():
|
36
|
-
return False
|
37
|
-
|
38
|
-
return True
|
39
|
-
|
40
|
-
except APIKey.DoesNotExist:
|
41
|
-
return False
|
42
|
-
except Exception as e:
|
43
|
-
logger.error(f"Error validating API key: {e}")
|
44
|
-
return False
|
45
|
-
|
46
|
-
|
47
|
-
def check_subscription_access(user_id: int, endpoint_group: str) -> Dict[str, Any]:
|
48
|
-
"""
|
49
|
-
Check subscription access for user and endpoint group.
|
50
|
-
|
51
|
-
Args:
|
52
|
-
user_id: User ID
|
53
|
-
endpoint_group: Endpoint group name
|
54
|
-
|
55
|
-
Returns:
|
56
|
-
Access check result dictionary
|
57
|
-
"""
|
58
|
-
try:
|
59
|
-
subscription = Subscription.objects.select_related('endpoint_group').get(
|
60
|
-
user_id=user_id,
|
61
|
-
endpoint_group__name=endpoint_group,
|
62
|
-
status='active',
|
63
|
-
expires_at__gt=timezone.now()
|
64
|
-
)
|
65
|
-
|
66
|
-
# Check usage limits
|
67
|
-
usage_percentage = (subscription.current_usage / subscription.monthly_limit) * 100
|
68
|
-
remaining_requests = subscription.monthly_limit - subscription.current_usage
|
69
|
-
|
70
|
-
return {
|
71
|
-
'allowed': remaining_requests > 0,
|
72
|
-
'subscription_id': str(subscription.id),
|
73
|
-
'remaining_requests': remaining_requests,
|
74
|
-
'usage_percentage': usage_percentage,
|
75
|
-
'reason': 'Active subscription' if remaining_requests > 0 else 'Usage limit exceeded'
|
76
|
-
}
|
77
|
-
|
78
|
-
except Subscription.DoesNotExist:
|
79
|
-
return {
|
80
|
-
'allowed': False,
|
81
|
-
'reason': 'No active subscription found',
|
82
|
-
'subscription_id': None,
|
83
|
-
'remaining_requests': 0,
|
84
|
-
'usage_percentage': 0
|
85
|
-
}
|
86
|
-
except Exception as e:
|
87
|
-
logger.error(f"Error checking subscription access: {e}")
|
88
|
-
return {
|
89
|
-
'allowed': False,
|
90
|
-
'reason': f'Access check failed: {str(e)}',
|
91
|
-
'subscription_id': None,
|
92
|
-
'remaining_requests': 0,
|
93
|
-
'usage_percentage': 0
|
94
|
-
}
|
@@ -1,63 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
DRF ViewSets for universal payments.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from .balance_views import UserBalanceViewSet, TransactionViewSet
|
6
|
-
from .payment_views import (
|
7
|
-
UserPaymentViewSet, UniversalPaymentViewSet,
|
8
|
-
PaymentCreateView, PaymentStatusView
|
9
|
-
)
|
10
|
-
from .subscription_views import (
|
11
|
-
UserSubscriptionViewSet, SubscriptionViewSet, EndpointGroupViewSet,
|
12
|
-
SubscriptionCreateView, ActiveSubscriptionsView
|
13
|
-
)
|
14
|
-
from .api_key_views import (
|
15
|
-
UserAPIKeyViewSet, APIKeyViewSet,
|
16
|
-
APIKeyCreateView, APIKeyValidateView
|
17
|
-
)
|
18
|
-
from .currency_views import (
|
19
|
-
CurrencyViewSet, NetworkViewSet, ProviderCurrencyViewSet,
|
20
|
-
SupportedCurrenciesView, CurrencyRatesView
|
21
|
-
)
|
22
|
-
from .tariff_views import (
|
23
|
-
TariffViewSet, TariffEndpointGroupViewSet,
|
24
|
-
AvailableTariffsView, TariffComparisonView
|
25
|
-
)
|
26
|
-
|
27
|
-
__all__ = [
|
28
|
-
# Balance ViewSets
|
29
|
-
'UserBalanceViewSet',
|
30
|
-
'TransactionViewSet',
|
31
|
-
|
32
|
-
# Payment ViewSets & Generics
|
33
|
-
'UserPaymentViewSet',
|
34
|
-
'UniversalPaymentViewSet',
|
35
|
-
'PaymentCreateView',
|
36
|
-
'PaymentStatusView',
|
37
|
-
|
38
|
-
# Subscription ViewSets & Generics
|
39
|
-
'UserSubscriptionViewSet',
|
40
|
-
'SubscriptionViewSet',
|
41
|
-
'EndpointGroupViewSet',
|
42
|
-
'SubscriptionCreateView',
|
43
|
-
'ActiveSubscriptionsView',
|
44
|
-
|
45
|
-
# API Key ViewSets & Generics
|
46
|
-
'UserAPIKeyViewSet',
|
47
|
-
'APIKeyViewSet',
|
48
|
-
'APIKeyCreateView',
|
49
|
-
'APIKeyValidateView',
|
50
|
-
|
51
|
-
# Currency ViewSets & Generics
|
52
|
-
'CurrencyViewSet',
|
53
|
-
'NetworkViewSet',
|
54
|
-
'ProviderCurrencyViewSet',
|
55
|
-
'SupportedCurrenciesView',
|
56
|
-
'CurrencyRatesView',
|
57
|
-
|
58
|
-
# Tariff ViewSets & Generics
|
59
|
-
'TariffViewSet',
|
60
|
-
'TariffEndpointGroupViewSet',
|
61
|
-
'AvailableTariffsView',
|
62
|
-
'TariffComparisonView',
|
63
|
-
]
|
@@ -1,164 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
API Key ViewSets with nested routing.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from rest_framework import viewsets, permissions, status, generics
|
6
|
-
from rest_framework.decorators import action
|
7
|
-
from rest_framework.response import Response
|
8
|
-
from django_filters.rest_framework import DjangoFilterBackend
|
9
|
-
from django.contrib.auth import get_user_model
|
10
|
-
from ..models import APIKey
|
11
|
-
from ..serializers import (
|
12
|
-
APIKeySerializer, APIKeyCreateSerializer, APIKeyListSerializer
|
13
|
-
)
|
14
|
-
|
15
|
-
User = get_user_model()
|
16
|
-
|
17
|
-
|
18
|
-
class UserAPIKeyViewSet(viewsets.ModelViewSet):
|
19
|
-
"""Nested ViewSet for user API keys: /users/{user_id}/api-keys/"""
|
20
|
-
|
21
|
-
serializer_class = APIKeySerializer
|
22
|
-
permission_classes = [permissions.IsAuthenticated]
|
23
|
-
filter_backends = [DjangoFilterBackend]
|
24
|
-
filterset_fields = ['is_active']
|
25
|
-
|
26
|
-
def get_queryset(self):
|
27
|
-
"""Filter by user from URL."""
|
28
|
-
user_id = self.kwargs.get('user_pk')
|
29
|
-
return APIKey.objects.filter(user_id=user_id).order_by('-created_at')
|
30
|
-
|
31
|
-
def get_serializer_class(self):
|
32
|
-
"""Use different serializers for different actions."""
|
33
|
-
if self.action == 'create':
|
34
|
-
return APIKeyCreateSerializer
|
35
|
-
elif self.action == 'list':
|
36
|
-
return APIKeyListSerializer
|
37
|
-
return APIKeySerializer
|
38
|
-
|
39
|
-
def perform_create(self, serializer):
|
40
|
-
"""Set user from URL when creating."""
|
41
|
-
user_id = self.kwargs.get('user_pk')
|
42
|
-
user = User.objects.get(id=user_id)
|
43
|
-
|
44
|
-
# Generate unique API key
|
45
|
-
import secrets
|
46
|
-
key_value = f"ak_{secrets.token_urlsafe(32)}"
|
47
|
-
|
48
|
-
serializer.save(user=user, key_value=key_value)
|
49
|
-
|
50
|
-
@action(detail=True, methods=['post'])
|
51
|
-
def regenerate(self, request, user_pk=None, pk=None):
|
52
|
-
"""Regenerate API key."""
|
53
|
-
api_key = self.get_object()
|
54
|
-
|
55
|
-
# Generate new key
|
56
|
-
import secrets
|
57
|
-
api_key.key_value = f"ak_{secrets.token_urlsafe(32)}"
|
58
|
-
api_key.usage_count = 0 # Reset usage
|
59
|
-
api_key.save()
|
60
|
-
|
61
|
-
serializer = self.get_serializer(api_key)
|
62
|
-
return Response(serializer.data)
|
63
|
-
|
64
|
-
@action(detail=True, methods=['post'])
|
65
|
-
def deactivate(self, request, user_pk=None, pk=None):
|
66
|
-
"""Deactivate API key."""
|
67
|
-
api_key = self.get_object()
|
68
|
-
api_key.is_active = False
|
69
|
-
api_key.save()
|
70
|
-
|
71
|
-
return Response({'message': 'API key deactivated'})
|
72
|
-
|
73
|
-
@action(detail=True, methods=['get'])
|
74
|
-
def usage_stats(self, request, user_pk=None, pk=None):
|
75
|
-
"""Get usage statistics for API key."""
|
76
|
-
api_key = self.get_object()
|
77
|
-
|
78
|
-
return Response({
|
79
|
-
'usage_count': api_key.usage_count,
|
80
|
-
'last_used': api_key.last_used,
|
81
|
-
'is_valid': api_key.is_valid(),
|
82
|
-
'expires_at': api_key.expires_at,
|
83
|
-
'is_active': api_key.is_active,
|
84
|
-
})
|
85
|
-
|
86
|
-
|
87
|
-
class APIKeyViewSet(viewsets.ReadOnlyModelViewSet):
|
88
|
-
"""Global API keys ViewSet: /api-keys/"""
|
89
|
-
|
90
|
-
queryset = APIKey.objects.all()
|
91
|
-
serializer_class = APIKeySerializer
|
92
|
-
permission_classes = [permissions.IsAuthenticated]
|
93
|
-
filter_backends = [DjangoFilterBackend]
|
94
|
-
filterset_fields = ['is_active']
|
95
|
-
|
96
|
-
def get_queryset(self):
|
97
|
-
"""Filter by current user for security."""
|
98
|
-
return APIKey.objects.filter(user=self.request.user).order_by('-created_at')
|
99
|
-
|
100
|
-
def get_serializer_class(self):
|
101
|
-
"""Use list serializer for list action."""
|
102
|
-
if self.action == 'list':
|
103
|
-
return APIKeyListSerializer
|
104
|
-
return APIKeySerializer
|
105
|
-
|
106
|
-
|
107
|
-
# Generic views for specific use cases
|
108
|
-
class APIKeyCreateView(generics.CreateAPIView):
|
109
|
-
"""Generic view to create API key."""
|
110
|
-
|
111
|
-
serializer_class = APIKeyCreateSerializer
|
112
|
-
permission_classes = [permissions.IsAuthenticated]
|
113
|
-
|
114
|
-
def perform_create(self, serializer):
|
115
|
-
"""Set current user and generate key when creating."""
|
116
|
-
import secrets
|
117
|
-
key_value = f"ak_{secrets.token_urlsafe(32)}"
|
118
|
-
serializer.save(user=self.request.user, key_value=key_value)
|
119
|
-
|
120
|
-
|
121
|
-
class APIKeyValidateView(generics.GenericAPIView):
|
122
|
-
"""Generic view to validate API key."""
|
123
|
-
|
124
|
-
serializer_class = APIKeySerializer # For schema generation
|
125
|
-
permission_classes = [permissions.AllowAny] # Public endpoint
|
126
|
-
|
127
|
-
def post(self, request):
|
128
|
-
"""Validate API key."""
|
129
|
-
key_value = request.data.get('api_key')
|
130
|
-
|
131
|
-
if not key_value:
|
132
|
-
return Response(
|
133
|
-
{'error': 'API key required'},
|
134
|
-
status=status.HTTP_400_BAD_REQUEST
|
135
|
-
)
|
136
|
-
|
137
|
-
try:
|
138
|
-
api_key = APIKey.objects.get(key_value=key_value, is_active=True)
|
139
|
-
|
140
|
-
# Check if expired
|
141
|
-
if api_key.is_expired:
|
142
|
-
return Response(
|
143
|
-
{'error': 'API key expired'},
|
144
|
-
status=status.HTTP_401_UNAUTHORIZED
|
145
|
-
)
|
146
|
-
|
147
|
-
# Update last used
|
148
|
-
from django.utils import timezone
|
149
|
-
api_key.last_used = timezone.now()
|
150
|
-
api_key.save()
|
151
|
-
|
152
|
-
return Response({
|
153
|
-
'valid': True,
|
154
|
-
'user_id': api_key.user.id,
|
155
|
-
'usage_count': api_key.usage_count,
|
156
|
-
'expires_at': api_key.expires_at,
|
157
|
-
'is_active': api_key.is_active,
|
158
|
-
})
|
159
|
-
|
160
|
-
except APIKey.DoesNotExist:
|
161
|
-
return Response(
|
162
|
-
{'valid': False, 'error': 'Invalid API key'},
|
163
|
-
status=status.HTTP_401_UNAUTHORIZED
|
164
|
-
)
|
@@ -1,75 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Balance ViewSets.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from rest_framework import viewsets, permissions, status
|
6
|
-
from rest_framework.decorators import action
|
7
|
-
from rest_framework.response import Response
|
8
|
-
from django_filters.rest_framework import DjangoFilterBackend
|
9
|
-
from ..models import UserBalance, Transaction
|
10
|
-
from ..serializers import (
|
11
|
-
UserBalanceSerializer, TransactionSerializer, TransactionListSerializer
|
12
|
-
)
|
13
|
-
|
14
|
-
|
15
|
-
class UserBalanceViewSet(viewsets.ReadOnlyModelViewSet):
|
16
|
-
"""User balance ViewSet - read only."""
|
17
|
-
|
18
|
-
queryset = UserBalance.objects.all()
|
19
|
-
serializer_class = UserBalanceSerializer
|
20
|
-
permission_classes = [permissions.IsAuthenticated]
|
21
|
-
|
22
|
-
def get_queryset(self):
|
23
|
-
"""Filter by current user."""
|
24
|
-
return UserBalance.objects.filter(user=self.request.user)
|
25
|
-
|
26
|
-
@action(detail=False, methods=['get'])
|
27
|
-
def current(self, request):
|
28
|
-
"""Get current user balance."""
|
29
|
-
balance, _ = UserBalance.objects.get_or_create(
|
30
|
-
user=request.user,
|
31
|
-
defaults={'amount_usd': 0.0, 'reserved_usd': 0.0}
|
32
|
-
)
|
33
|
-
serializer = self.get_serializer(balance)
|
34
|
-
return Response(serializer.data)
|
35
|
-
|
36
|
-
|
37
|
-
class TransactionViewSet(viewsets.ReadOnlyModelViewSet):
|
38
|
-
"""Transaction ViewSet - read only."""
|
39
|
-
|
40
|
-
queryset = Transaction.objects.all()
|
41
|
-
serializer_class = TransactionSerializer
|
42
|
-
permission_classes = [permissions.IsAuthenticated]
|
43
|
-
filter_backends = [DjangoFilterBackend]
|
44
|
-
filterset_fields = ['transaction_type', 'payment', 'subscription']
|
45
|
-
|
46
|
-
def get_queryset(self):
|
47
|
-
"""Filter by current user."""
|
48
|
-
return Transaction.objects.filter(user=self.request.user).order_by('-created_at')
|
49
|
-
|
50
|
-
def get_serializer_class(self):
|
51
|
-
"""Use list serializer for list action."""
|
52
|
-
if self.action == 'list':
|
53
|
-
return TransactionListSerializer
|
54
|
-
return TransactionSerializer
|
55
|
-
|
56
|
-
@action(detail=False, methods=['get'])
|
57
|
-
def summary(self, request):
|
58
|
-
"""Get transaction summary."""
|
59
|
-
queryset = self.get_queryset()
|
60
|
-
|
61
|
-
total_earned = sum(
|
62
|
-
t.amount_usd for t in queryset
|
63
|
-
if t.amount_usd > 0
|
64
|
-
)
|
65
|
-
total_spent = sum(
|
66
|
-
abs(t.amount_usd) for t in queryset
|
67
|
-
if t.amount_usd < 0
|
68
|
-
)
|
69
|
-
|
70
|
-
return Response({
|
71
|
-
'total_transactions': queryset.count(),
|
72
|
-
'total_earned': total_earned,
|
73
|
-
'total_spent': total_spent,
|
74
|
-
'net_balance': total_earned - total_spent
|
75
|
-
})
|