django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.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
+ ]