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