django-cfg 1.2.31__py3-none-any.whl → 1.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.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
+ ]