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
@@ -0,0 +1,476 @@
|
|
1
|
+
"""
|
2
|
+
Webhook API ViewSets for Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Handles incoming webhooks from payment providers with universal support.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
from typing import Dict, Any, Optional
|
10
|
+
from django.http import JsonResponse, HttpRequest
|
11
|
+
from django.views.decorators.csrf import csrf_exempt
|
12
|
+
from django.views.decorators.http import require_http_methods
|
13
|
+
from django.utils.decorators import method_decorator
|
14
|
+
from django.utils import timezone
|
15
|
+
from rest_framework import status
|
16
|
+
from rest_framework.decorators import api_view, permission_classes
|
17
|
+
from rest_framework.permissions import AllowAny
|
18
|
+
from rest_framework.response import Response
|
19
|
+
from rest_framework.views import APIView
|
20
|
+
|
21
|
+
from ...services.core.webhook_service import WebhookService
|
22
|
+
from ...services.types import WebhookValidationRequest, WebhookProcessingResult
|
23
|
+
from django_cfg.modules.django_logger import get_logger
|
24
|
+
|
25
|
+
logger = get_logger("webhook_views")
|
26
|
+
|
27
|
+
|
28
|
+
@method_decorator(csrf_exempt, name='dispatch')
|
29
|
+
class UniversalWebhookView(APIView):
|
30
|
+
"""
|
31
|
+
Universal webhook handler for all payment providers.
|
32
|
+
|
33
|
+
Features:
|
34
|
+
- Provider-agnostic webhook processing
|
35
|
+
- Signature validation and security
|
36
|
+
- Idempotency and replay protection
|
37
|
+
- Comprehensive logging and monitoring
|
38
|
+
- Integration with ngrok for development
|
39
|
+
"""
|
40
|
+
|
41
|
+
permission_classes = [AllowAny] # Webhooks don't use standard auth
|
42
|
+
|
43
|
+
def __init__(self, **kwargs):
|
44
|
+
super().__init__(**kwargs)
|
45
|
+
self.webhook_service = WebhookService()
|
46
|
+
|
47
|
+
def post(self, request: HttpRequest, provider: str) -> JsonResponse:
|
48
|
+
"""
|
49
|
+
Handle incoming webhook from any payment provider.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
request: HTTP request with webhook payload
|
53
|
+
provider: Provider name (nowpayments, cryptapi, etc.)
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
JsonResponse: Processing result
|
57
|
+
"""
|
58
|
+
|
59
|
+
start_time = timezone.now()
|
60
|
+
request_id = self._generate_request_id()
|
61
|
+
|
62
|
+
logger.info(f"📥 Webhook received", extra={
|
63
|
+
'provider': provider,
|
64
|
+
'request_id': request_id,
|
65
|
+
'content_type': request.content_type,
|
66
|
+
'content_length': len(request.body) if request.body else 0,
|
67
|
+
'user_agent': request.META.get('HTTP_USER_AGENT', 'unknown')
|
68
|
+
})
|
69
|
+
|
70
|
+
try:
|
71
|
+
# Parse webhook payload
|
72
|
+
webhook_payload = self._parse_webhook_payload(request)
|
73
|
+
if not webhook_payload:
|
74
|
+
return self._error_response(
|
75
|
+
"Invalid webhook payload",
|
76
|
+
status.HTTP_400_BAD_REQUEST,
|
77
|
+
request_id
|
78
|
+
)
|
79
|
+
|
80
|
+
# Extract headers for signature validation
|
81
|
+
webhook_headers = self._extract_webhook_headers(request)
|
82
|
+
|
83
|
+
# Get signature from headers (provider-specific)
|
84
|
+
signature = self._extract_signature(provider, webhook_headers)
|
85
|
+
|
86
|
+
# Create validation request
|
87
|
+
validation_request = WebhookValidationRequest(
|
88
|
+
provider=provider,
|
89
|
+
payload=webhook_payload,
|
90
|
+
signature=signature,
|
91
|
+
headers=webhook_headers,
|
92
|
+
timestamp=start_time.isoformat(),
|
93
|
+
request_id=request_id
|
94
|
+
)
|
95
|
+
|
96
|
+
# Process webhook
|
97
|
+
result = self.webhook_service.process_webhook(validation_request)
|
98
|
+
|
99
|
+
# Log processing result
|
100
|
+
processing_time = (timezone.now() - start_time).total_seconds()
|
101
|
+
|
102
|
+
logger.info(f"🔄 Webhook processed", extra={
|
103
|
+
'provider': provider,
|
104
|
+
'request_id': request_id,
|
105
|
+
'success': result.success,
|
106
|
+
'processing_time_ms': round(processing_time * 1000, 2),
|
107
|
+
'actions_taken': getattr(result, 'actions_taken', []),
|
108
|
+
'payment_id': getattr(result, 'payment_id', None)
|
109
|
+
})
|
110
|
+
|
111
|
+
# Return appropriate response
|
112
|
+
if result.success:
|
113
|
+
return self._success_response(result, request_id)
|
114
|
+
else:
|
115
|
+
return self._error_response(
|
116
|
+
result.error_message,
|
117
|
+
status.HTTP_400_BAD_REQUEST if 'validation' in result.error_message.lower() else status.HTTP_500_INTERNAL_SERVER_ERROR,
|
118
|
+
request_id,
|
119
|
+
result
|
120
|
+
)
|
121
|
+
|
122
|
+
except json.JSONDecodeError as e:
|
123
|
+
logger.warning(f"Invalid JSON payload", extra={
|
124
|
+
'provider': provider,
|
125
|
+
'request_id': request_id,
|
126
|
+
'error': str(e)
|
127
|
+
})
|
128
|
+
return self._error_response(
|
129
|
+
"Invalid JSON payload",
|
130
|
+
status.HTTP_400_BAD_REQUEST,
|
131
|
+
request_id
|
132
|
+
)
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
logger.error(f"Webhook processing error", extra={
|
136
|
+
'provider': provider,
|
137
|
+
'request_id': request_id,
|
138
|
+
'error': str(e),
|
139
|
+
'error_type': type(e).__name__
|
140
|
+
})
|
141
|
+
return self._error_response(
|
142
|
+
"Internal server error",
|
143
|
+
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
144
|
+
request_id
|
145
|
+
)
|
146
|
+
|
147
|
+
def get(self, request: HttpRequest, provider: str) -> JsonResponse:
|
148
|
+
"""
|
149
|
+
Handle GET requests for webhook endpoint info.
|
150
|
+
|
151
|
+
Useful for debugging and provider configuration verification.
|
152
|
+
"""
|
153
|
+
|
154
|
+
logger.info(f"📋 Webhook info requested", extra={
|
155
|
+
'provider': provider,
|
156
|
+
'ip': self._get_client_ip(request)
|
157
|
+
})
|
158
|
+
|
159
|
+
# Get webhook URL using ngrok integration
|
160
|
+
webhook_url = self._get_webhook_url(request, provider)
|
161
|
+
|
162
|
+
info = {
|
163
|
+
'provider': provider,
|
164
|
+
'webhook_url': webhook_url,
|
165
|
+
'supported_methods': ['POST'],
|
166
|
+
'expected_headers': self._get_expected_headers(provider),
|
167
|
+
'timestamp': timezone.now().isoformat(),
|
168
|
+
'service_status': 'active'
|
169
|
+
}
|
170
|
+
|
171
|
+
return JsonResponse(info, status=status.HTTP_200_OK)
|
172
|
+
|
173
|
+
# ===== HELPER METHODS =====
|
174
|
+
|
175
|
+
def _parse_webhook_payload(self, request: HttpRequest) -> Optional[Dict[str, Any]]:
|
176
|
+
"""Parse webhook payload from request body."""
|
177
|
+
try:
|
178
|
+
if not request.body:
|
179
|
+
return None
|
180
|
+
|
181
|
+
# Handle different content types
|
182
|
+
content_type = request.content_type.lower()
|
183
|
+
|
184
|
+
if 'application/json' in content_type:
|
185
|
+
return json.loads(request.body.decode('utf-8'))
|
186
|
+
elif 'application/x-www-form-urlencoded' in content_type:
|
187
|
+
# Some providers send form data
|
188
|
+
from urllib.parse import parse_qs
|
189
|
+
parsed = parse_qs(request.body.decode('utf-8'))
|
190
|
+
# Convert single-item lists to values
|
191
|
+
return {k: v[0] if len(v) == 1 else v for k, v in parsed.items()}
|
192
|
+
else:
|
193
|
+
# Try JSON as fallback
|
194
|
+
return json.loads(request.body.decode('utf-8'))
|
195
|
+
|
196
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
197
|
+
logger.warning(f"Failed to parse webhook payload: {e}")
|
198
|
+
return None
|
199
|
+
|
200
|
+
def _extract_webhook_headers(self, request: HttpRequest) -> Dict[str, str]:
|
201
|
+
"""Extract relevant headers for webhook validation."""
|
202
|
+
headers = {}
|
203
|
+
|
204
|
+
# Extract all HTTP headers
|
205
|
+
for key, value in request.META.items():
|
206
|
+
if key.startswith('HTTP_'):
|
207
|
+
# Convert HTTP_X_CUSTOM_HEADER to x-custom-header
|
208
|
+
header_name = key[5:].lower().replace('_', '-')
|
209
|
+
headers[header_name] = value
|
210
|
+
|
211
|
+
# Add some non-HTTP headers that might be useful
|
212
|
+
headers['content-type'] = request.content_type
|
213
|
+
headers['content-length'] = str(len(request.body)) if request.body else '0'
|
214
|
+
|
215
|
+
return headers
|
216
|
+
|
217
|
+
def _extract_signature(self, provider: str, headers: Dict[str, str]) -> Optional[str]:
|
218
|
+
"""Extract signature from headers based on provider."""
|
219
|
+
|
220
|
+
# Provider-specific signature header mapping
|
221
|
+
signature_headers = {
|
222
|
+
'nowpayments': 'x-nowpayments-sig',
|
223
|
+
'stripe': 'stripe-signature',
|
224
|
+
'cryptapi': 'x-cryptapi-signature',
|
225
|
+
'cryptomus': 'sign',
|
226
|
+
}
|
227
|
+
|
228
|
+
signature_header = signature_headers.get(provider.lower())
|
229
|
+
if signature_header:
|
230
|
+
return headers.get(signature_header)
|
231
|
+
|
232
|
+
# Fallback: look for common signature headers
|
233
|
+
common_headers = ['signature', 'x-signature', 'authorization']
|
234
|
+
for header in common_headers:
|
235
|
+
if header in headers:
|
236
|
+
return headers[header]
|
237
|
+
|
238
|
+
return None
|
239
|
+
|
240
|
+
def _get_expected_headers(self, provider: str) -> Dict[str, str]:
|
241
|
+
"""Get expected headers for each provider."""
|
242
|
+
from ...services.integrations import get_webhook_provider_info, is_provider_supported
|
243
|
+
|
244
|
+
if not is_provider_supported(provider):
|
245
|
+
return {'content-type': 'application/json'}
|
246
|
+
|
247
|
+
provider_info = get_webhook_provider_info(provider)
|
248
|
+
return {
|
249
|
+
provider_info.signature_header: f'{provider_info.signature_algorithm} signature',
|
250
|
+
'content-type': provider_info.content_type
|
251
|
+
}
|
252
|
+
|
253
|
+
def _get_webhook_url(self, request: HttpRequest, provider: str) -> str:
|
254
|
+
"""Get webhook URL using ngrok integration."""
|
255
|
+
from ...services.integrations import get_webhook_url_for_provider
|
256
|
+
return get_webhook_url_for_provider(provider)
|
257
|
+
|
258
|
+
def _generate_request_id(self) -> str:
|
259
|
+
"""Generate unique request ID for tracking."""
|
260
|
+
import uuid
|
261
|
+
return str(uuid.uuid4())[:8]
|
262
|
+
|
263
|
+
def _get_client_ip(self, request: HttpRequest) -> str:
|
264
|
+
"""Get client IP address."""
|
265
|
+
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
266
|
+
if x_forwarded_for:
|
267
|
+
return x_forwarded_for.split(',')[0].strip()
|
268
|
+
return request.META.get('REMOTE_ADDR', 'unknown')
|
269
|
+
|
270
|
+
def _success_response(self, result: WebhookProcessingResult, request_id: str) -> JsonResponse:
|
271
|
+
"""Create success response."""
|
272
|
+
|
273
|
+
response_data = {
|
274
|
+
'success': True,
|
275
|
+
'message': 'Webhook processed successfully',
|
276
|
+
'request_id': request_id,
|
277
|
+
'provider': result.provider,
|
278
|
+
'processed': result.processed,
|
279
|
+
'timestamp': timezone.now().isoformat()
|
280
|
+
}
|
281
|
+
|
282
|
+
# Add optional fields if available
|
283
|
+
if hasattr(result, 'payment_id') and result.payment_id:
|
284
|
+
response_data['payment_id'] = result.payment_id
|
285
|
+
|
286
|
+
if hasattr(result, 'actions_taken') and result.actions_taken:
|
287
|
+
response_data['actions_taken'] = result.actions_taken
|
288
|
+
|
289
|
+
if hasattr(result, 'status_after') and result.status_after:
|
290
|
+
response_data['payment_status'] = result.status_after
|
291
|
+
|
292
|
+
return JsonResponse(response_data, status=status.HTTP_200_OK)
|
293
|
+
|
294
|
+
def _error_response(
|
295
|
+
self,
|
296
|
+
message: str,
|
297
|
+
status_code: int,
|
298
|
+
request_id: str,
|
299
|
+
result: Optional[WebhookProcessingResult] = None
|
300
|
+
) -> JsonResponse:
|
301
|
+
"""Create error response."""
|
302
|
+
|
303
|
+
response_data = {
|
304
|
+
'success': False,
|
305
|
+
'error': message,
|
306
|
+
'request_id': request_id,
|
307
|
+
'timestamp': timezone.now().isoformat()
|
308
|
+
}
|
309
|
+
|
310
|
+
if result:
|
311
|
+
response_data['provider'] = result.provider
|
312
|
+
response_data['processed'] = getattr(result, 'processed', False)
|
313
|
+
|
314
|
+
return JsonResponse(response_data, status=status_code)
|
315
|
+
|
316
|
+
|
317
|
+
@api_view(['GET'])
|
318
|
+
@permission_classes([AllowAny])
|
319
|
+
def webhook_health_check(request):
|
320
|
+
"""
|
321
|
+
Health check endpoint for webhook service.
|
322
|
+
|
323
|
+
Returns service status and recent activity metrics.
|
324
|
+
"""
|
325
|
+
|
326
|
+
try:
|
327
|
+
from ...services.integrations import is_ngrok_available, get_api_base_url
|
328
|
+
|
329
|
+
webhook_service = WebhookService()
|
330
|
+
health_result = webhook_service.health_check()
|
331
|
+
|
332
|
+
# Add ngrok status
|
333
|
+
health_data = health_result.data if health_result.success else {}
|
334
|
+
health_data.update({
|
335
|
+
'ngrok_available': is_ngrok_available(),
|
336
|
+
'api_base_url': get_api_base_url(),
|
337
|
+
})
|
338
|
+
|
339
|
+
if health_result.success:
|
340
|
+
return Response({
|
341
|
+
'status': 'healthy',
|
342
|
+
'service': 'webhook_service',
|
343
|
+
'timestamp': timezone.now().isoformat(),
|
344
|
+
'details': health_data
|
345
|
+
}, status=status.HTTP_200_OK)
|
346
|
+
else:
|
347
|
+
return Response({
|
348
|
+
'status': 'unhealthy',
|
349
|
+
'service': 'webhook_service',
|
350
|
+
'error': health_result.message,
|
351
|
+
'timestamp': timezone.now().isoformat(),
|
352
|
+
'details': health_data
|
353
|
+
}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
354
|
+
|
355
|
+
except Exception as e:
|
356
|
+
logger.error(f"Webhook health check failed: {e}")
|
357
|
+
return Response({
|
358
|
+
'status': 'error',
|
359
|
+
'service': 'webhook_service',
|
360
|
+
'error': str(e),
|
361
|
+
'timestamp': timezone.now().isoformat()
|
362
|
+
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
363
|
+
|
364
|
+
|
365
|
+
@api_view(['GET'])
|
366
|
+
@permission_classes([AllowAny])
|
367
|
+
def webhook_stats(request):
|
368
|
+
"""
|
369
|
+
Get webhook processing statistics.
|
370
|
+
|
371
|
+
Query parameters:
|
372
|
+
- days: Number of days to analyze (default: 30)
|
373
|
+
"""
|
374
|
+
|
375
|
+
try:
|
376
|
+
days = int(request.GET.get('days', 30))
|
377
|
+
if days < 1 or days > 365:
|
378
|
+
days = 30
|
379
|
+
|
380
|
+
webhook_service = WebhookService()
|
381
|
+
stats_result = webhook_service.get_webhook_stats(days)
|
382
|
+
|
383
|
+
if stats_result.success:
|
384
|
+
return Response({
|
385
|
+
'success': True,
|
386
|
+
'stats': stats_result.data,
|
387
|
+
'timestamp': timezone.now().isoformat()
|
388
|
+
}, status=status.HTTP_200_OK)
|
389
|
+
else:
|
390
|
+
return Response({
|
391
|
+
'success': False,
|
392
|
+
'error': stats_result.message,
|
393
|
+
'timestamp': timezone.now().isoformat()
|
394
|
+
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
395
|
+
|
396
|
+
except ValueError:
|
397
|
+
return Response({
|
398
|
+
'success': False,
|
399
|
+
'error': 'Invalid days parameter',
|
400
|
+
'timestamp': timezone.now().isoformat()
|
401
|
+
}, status=status.HTTP_400_BAD_REQUEST)
|
402
|
+
except Exception as e:
|
403
|
+
logger.error(f"Webhook stats failed: {e}")
|
404
|
+
return Response({
|
405
|
+
'success': False,
|
406
|
+
'error': str(e),
|
407
|
+
'timestamp': timezone.now().isoformat()
|
408
|
+
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
409
|
+
|
410
|
+
|
411
|
+
@api_view(['GET'])
|
412
|
+
@permission_classes([AllowAny])
|
413
|
+
def supported_providers(request):
|
414
|
+
"""
|
415
|
+
Get list of supported webhook providers.
|
416
|
+
|
417
|
+
Returns provider information and webhook URLs.
|
418
|
+
"""
|
419
|
+
|
420
|
+
try:
|
421
|
+
from ...services.integrations import get_all_webhook_urls, get_all_providers_info
|
422
|
+
|
423
|
+
# Get all providers info dynamically
|
424
|
+
providers_info = get_all_providers_info()
|
425
|
+
webhook_urls = get_all_webhook_urls()
|
426
|
+
|
427
|
+
# Build provider list
|
428
|
+
providers_list = []
|
429
|
+
for provider_name, info in providers_info.items():
|
430
|
+
providers_list.append({
|
431
|
+
'name': provider_name,
|
432
|
+
'display_name': info['display_name'],
|
433
|
+
'signature_header': info['signature_header'],
|
434
|
+
'signature_algorithm': info['signature_algorithm'],
|
435
|
+
'webhook_url': webhook_urls.get(provider_name, f"http://localhost:8000/api/webhooks/{provider_name}/"),
|
436
|
+
'content_type': info['content_type']
|
437
|
+
})
|
438
|
+
|
439
|
+
return Response({
|
440
|
+
'success': True,
|
441
|
+
'providers': providers_list,
|
442
|
+
'total_count': len(providers_list),
|
443
|
+
'timestamp': timezone.now().isoformat()
|
444
|
+
}, status=status.HTTP_200_OK)
|
445
|
+
|
446
|
+
except Exception as e:
|
447
|
+
logger.error(f"Supported providers endpoint failed: {e}")
|
448
|
+
return Response({
|
449
|
+
'success': False,
|
450
|
+
'error': str(e),
|
451
|
+
'timestamp': timezone.now().isoformat()
|
452
|
+
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
453
|
+
|
454
|
+
|
455
|
+
# ===== LEGACY FUNCTION-BASED VIEW FOR COMPATIBILITY =====
|
456
|
+
|
457
|
+
@csrf_exempt
|
458
|
+
@require_http_methods(["POST", "GET"])
|
459
|
+
def webhook_handler(request, provider: str):
|
460
|
+
"""
|
461
|
+
Legacy function-based webhook handler for compatibility.
|
462
|
+
|
463
|
+
Delegates to UniversalWebhookView for actual processing.
|
464
|
+
"""
|
465
|
+
|
466
|
+
view = UniversalWebhookView()
|
467
|
+
|
468
|
+
if request.method == 'POST':
|
469
|
+
return view.post(request, provider)
|
470
|
+
elif request.method == 'GET':
|
471
|
+
return view.get(request, provider)
|
472
|
+
else:
|
473
|
+
return JsonResponse({
|
474
|
+
'error': 'Method not allowed',
|
475
|
+
'allowed_methods': ['POST', 'GET']
|
476
|
+
}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
"""
|
2
|
+
Serializers for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Django REST Framework serializers with Pydantic integration and service layer validation.
|
5
|
+
"""
|
6
|
+
|
7
|
+
# Payment serializers
|
8
|
+
from .payments import (
|
9
|
+
PaymentSerializer,
|
10
|
+
PaymentCreateSerializer,
|
11
|
+
PaymentListSerializer,
|
12
|
+
PaymentStatusSerializer,
|
13
|
+
)
|
14
|
+
|
15
|
+
# Balance serializers
|
16
|
+
from .balances import (
|
17
|
+
UserBalanceSerializer,
|
18
|
+
TransactionSerializer,
|
19
|
+
BalanceUpdateSerializer,
|
20
|
+
)
|
21
|
+
|
22
|
+
# Subscription serializers
|
23
|
+
from .subscriptions import (
|
24
|
+
SubscriptionSerializer,
|
25
|
+
SubscriptionCreateSerializer,
|
26
|
+
SubscriptionListSerializer,
|
27
|
+
SubscriptionUpdateSerializer,
|
28
|
+
SubscriptionUsageSerializer,
|
29
|
+
SubscriptionStatsSerializer,
|
30
|
+
EndpointGroupSerializer,
|
31
|
+
TariffSerializer,
|
32
|
+
)
|
33
|
+
|
34
|
+
# Currency serializers
|
35
|
+
from .currencies import (
|
36
|
+
CurrencySerializer,
|
37
|
+
NetworkSerializer,
|
38
|
+
ProviderCurrencySerializer,
|
39
|
+
CurrencyConversionSerializer,
|
40
|
+
)
|
41
|
+
|
42
|
+
# API Key serializers
|
43
|
+
from .api_keys import (
|
44
|
+
APIKeySerializer,
|
45
|
+
APIKeyCreateSerializer,
|
46
|
+
APIKeyListSerializer,
|
47
|
+
APIKeyUpdateSerializer,
|
48
|
+
APIKeyActionSerializer,
|
49
|
+
APIKeyValidationSerializer,
|
50
|
+
APIKeyStatsSerializer,
|
51
|
+
)
|
52
|
+
|
53
|
+
# Webhook serializers
|
54
|
+
from .webhooks import (
|
55
|
+
WebhookSerializer,
|
56
|
+
NowPaymentsWebhookSerializer,
|
57
|
+
)
|
58
|
+
|
59
|
+
__all__ = [
|
60
|
+
# Payment serializers
|
61
|
+
'PaymentSerializer',
|
62
|
+
'PaymentCreateSerializer',
|
63
|
+
'PaymentListSerializer',
|
64
|
+
'PaymentStatusSerializer',
|
65
|
+
|
66
|
+
# Balance serializers
|
67
|
+
'UserBalanceSerializer',
|
68
|
+
'TransactionSerializer',
|
69
|
+
'BalanceUpdateSerializer',
|
70
|
+
|
71
|
+
# Subscription serializers
|
72
|
+
'SubscriptionSerializer',
|
73
|
+
'SubscriptionCreateSerializer',
|
74
|
+
'SubscriptionListSerializer',
|
75
|
+
'SubscriptionUpdateSerializer',
|
76
|
+
'SubscriptionUsageSerializer',
|
77
|
+
'SubscriptionStatsSerializer',
|
78
|
+
'EndpointGroupSerializer',
|
79
|
+
'TariffSerializer',
|
80
|
+
|
81
|
+
# Currency serializers
|
82
|
+
'CurrencySerializer',
|
83
|
+
'NetworkSerializer',
|
84
|
+
'ProviderCurrencySerializer',
|
85
|
+
'CurrencyConversionSerializer',
|
86
|
+
|
87
|
+
# API Key serializers
|
88
|
+
'APIKeySerializer',
|
89
|
+
'APIKeyCreateSerializer',
|
90
|
+
'APIKeyListSerializer',
|
91
|
+
'APIKeyUpdateSerializer',
|
92
|
+
'APIKeyActionSerializer',
|
93
|
+
'APIKeyValidationSerializer',
|
94
|
+
'APIKeyStatsSerializer',
|
95
|
+
|
96
|
+
# Webhook serializers
|
97
|
+
'WebhookSerializer',
|
98
|
+
'NowPaymentsWebhookSerializer',
|
99
|
+
]
|