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
@@ -1,244 +0,0 @@
1
- """
2
- Payment statistics and analytics views.
3
-
4
- Provides comprehensive analytics for payment performance and trends.
5
- """
6
-
7
- from django.views.generic import TemplateView
8
- from django.utils import timezone
9
- from django.db.models import Q
10
- from datetime import timedelta
11
- from .base import (
12
- SuperuserRequiredMixin,
13
- PaymentStatsMixin,
14
- PaymentContextMixin,
15
- log_view_access
16
- )
17
-
18
-
19
- class PaymentStatsView(
20
- SuperuserRequiredMixin,
21
- PaymentStatsMixin,
22
- PaymentContextMixin,
23
- TemplateView
24
- ):
25
- """Analytics and statistics view."""
26
-
27
- template_name = 'payments/stats.html'
28
- page_title = 'Payment Analytics'
29
-
30
- def get_breadcrumbs(self):
31
- return [
32
- {'name': 'Dashboard', 'url': '/payments/admin/'},
33
- {'name': 'Analytics', 'url': ''},
34
- ]
35
-
36
- def get_context_data(self, **kwargs):
37
- context = super().get_context_data(**kwargs)
38
-
39
- # Log access for audit
40
- log_view_access('payment_stats', self.request.user)
41
-
42
- # Get time period from request (default to 30 days)
43
- days = int(self.request.GET.get('days', 30))
44
-
45
- # Calculate date ranges
46
- now = timezone.now()
47
- ranges = self._get_date_ranges(now, days)
48
-
49
- # Get comprehensive statistics
50
- stats = {
51
- 'overview': self._get_overview_stats(),
52
- 'time_periods': self._get_time_period_stats(ranges),
53
- 'providers': self._get_detailed_provider_stats(),
54
- 'status_distribution': self._get_status_distribution(),
55
- 'trends': self._get_trend_data(days),
56
- 'performance': self._get_performance_metrics(),
57
- }
58
-
59
- # Get common context
60
- common_context = self.get_common_context()
61
-
62
- context.update({
63
- 'stats': stats,
64
- 'selected_days': days,
65
- 'available_periods': [7, 30, 90, 365],
66
- 'date_ranges': ranges,
67
- **common_context
68
- })
69
-
70
- return context
71
-
72
- def _get_date_ranges(self, now, days):
73
- """Calculate various date ranges for statistics."""
74
- return {
75
- 'current_period_start': now - timedelta(days=days),
76
- 'current_period_end': now,
77
- 'previous_period_start': now - timedelta(days=days * 2),
78
- 'previous_period_end': now - timedelta(days=days),
79
- 'last_7_days': now - timedelta(days=7),
80
- 'last_30_days': now - timedelta(days=30),
81
- 'last_year': now - timedelta(days=365),
82
- }
83
-
84
- def _get_overview_stats(self):
85
- """Get overall payment statistics."""
86
- return self.get_payment_stats()
87
-
88
- def _get_time_period_stats(self, ranges):
89
- """Get statistics for different time periods."""
90
- from ...models import UniversalPayment
91
-
92
- periods = {}
93
-
94
- # Current period
95
- current_qs = UniversalPayment.objects.filter(
96
- created_at__gte=ranges['current_period_start'],
97
- created_at__lte=ranges['current_period_end']
98
- )
99
- periods['current'] = self.get_payment_stats(current_qs)
100
-
101
- # Previous period for comparison
102
- previous_qs = UniversalPayment.objects.filter(
103
- created_at__gte=ranges['previous_period_start'],
104
- created_at__lte=ranges['previous_period_end']
105
- )
106
- periods['previous'] = self.get_payment_stats(previous_qs)
107
-
108
- # Calculate growth rates
109
- periods['growth'] = self._calculate_growth_rates(
110
- periods['current'],
111
- periods['previous']
112
- )
113
-
114
- # Last 7 days
115
- last_7_qs = UniversalPayment.objects.filter(created_at__gte=ranges['last_7_days'])
116
- periods['last_7_days'] = self.get_payment_stats(last_7_qs)
117
-
118
- # Last 30 days
119
- last_30_qs = UniversalPayment.objects.filter(created_at__gte=ranges['last_30_days'])
120
- periods['last_30_days'] = self.get_payment_stats(last_30_qs)
121
-
122
- return periods
123
-
124
- def _get_detailed_provider_stats(self):
125
- """Get detailed provider statistics."""
126
- provider_stats = self.get_provider_stats()
127
-
128
- # Add additional metrics for each provider
129
- for stat in provider_stats:
130
- stat['avg_amount'] = stat['volume'] / stat['count'] if stat['count'] > 0 else 0
131
- stat['failure_rate'] = 100 - stat['success_rate']
132
-
133
- return provider_stats
134
-
135
- def _get_status_distribution(self):
136
- """Get payment status distribution."""
137
- from ...models import UniversalPayment
138
- from django.db.models import Count
139
-
140
- distribution = UniversalPayment.objects.values('status').annotate(
141
- count=Count('id')
142
- ).order_by('-count')
143
-
144
- total = sum(item['count'] for item in distribution)
145
-
146
- # Add percentage
147
- for item in distribution:
148
- item['percentage'] = (item['count'] / total * 100) if total > 0 else 0
149
-
150
- return distribution
151
-
152
- def _get_trend_data(self, days):
153
- """Get trend data for charts."""
154
- from ...models import UniversalPayment
155
- from django.db.models import Count, Sum
156
- from django.db.models.functions import TruncDate
157
-
158
- end_date = timezone.now()
159
- start_date = end_date - timedelta(days=days)
160
-
161
- # Daily trends
162
- daily_trends = UniversalPayment.objects.filter(
163
- created_at__gte=start_date,
164
- created_at__lte=end_date
165
- ).annotate(
166
- date=TruncDate('created_at')
167
- ).values('date').annotate(
168
- count=Count('id'),
169
- volume=Sum('amount_usd'),
170
- completed=Count('id', filter=Q(status='completed'))
171
- ).order_by('date')
172
-
173
- # Convert to list and add calculated fields
174
- trends = []
175
- for item in daily_trends:
176
- trends.append({
177
- 'date': item['date'].isoformat(),
178
- 'count': item['count'],
179
- 'volume': float(item['volume'] or 0),
180
- 'completed': item['completed'],
181
- 'success_rate': (item['completed'] / item['count'] * 100) if item['count'] > 0 else 0
182
- })
183
-
184
- return trends
185
-
186
- def _get_performance_metrics(self):
187
- """Get performance metrics."""
188
- from ...models import UniversalPayment, PaymentEvent
189
- from django.db.models import Avg, Min, Max
190
-
191
- # Average processing time (from created to completed)
192
- completed_payments = UniversalPayment.objects.filter(
193
- status='completed',
194
- completed_at__isnull=False
195
- )
196
-
197
- processing_times = []
198
- for payment in completed_payments[:100]: # Sample for performance
199
- if payment.completed_at and payment.created_at:
200
- duration = payment.completed_at - payment.created_at
201
- processing_times.append(duration.total_seconds())
202
-
203
- metrics = {
204
- 'avg_processing_time': sum(processing_times) / len(processing_times) if processing_times else 0,
205
- 'min_processing_time': min(processing_times) if processing_times else 0,
206
- 'max_processing_time': max(processing_times) if processing_times else 0,
207
- }
208
-
209
- # Convert seconds to human readable format
210
- formatted_metrics = {}
211
- for key, value in metrics.items():
212
- if value > 0:
213
- formatted_metrics[f"{key}_formatted"] = self._format_duration(value)
214
- else:
215
- formatted_metrics[f"{key}_formatted"] = "N/A"
216
-
217
- # Add formatted metrics to the original metrics
218
- metrics.update(formatted_metrics)
219
-
220
- return metrics
221
-
222
- def _calculate_growth_rates(self, current, previous):
223
- """Calculate growth rates between two periods."""
224
- growth = {}
225
-
226
- for key in ['total_count', 'total_volume', 'completed_count']:
227
- current_val = current.get(key, 0)
228
- previous_val = previous.get(key, 0)
229
-
230
- if previous_val > 0:
231
- growth[f"{key}_rate"] = ((current_val - previous_val) / previous_val) * 100
232
- else:
233
- growth[f"{key}_rate"] = 100 if current_val > 0 else 0
234
-
235
- return growth
236
-
237
- def _format_duration(self, seconds):
238
- """Format duration in seconds to human readable format."""
239
- if seconds < 60:
240
- return f"{seconds:.1f}s"
241
- elif seconds < 3600:
242
- return f"{seconds/60:.1f}m"
243
- else:
244
- return f"{seconds/3600:.1f}h"
@@ -1,181 +0,0 @@
1
- """
2
- Utility views for payment dashboard.
3
-
4
- Provides testing, debugging, and development functionality.
5
- """
6
-
7
- from django.views.generic import TemplateView
8
- from django.utils import timezone
9
- from datetime import timedelta
10
- from .base import (
11
- SuperuserRequiredMixin,
12
- PaymentContextMixin,
13
- log_view_access
14
- )
15
-
16
-
17
- class PaymentTestView(
18
- SuperuserRequiredMixin,
19
- PaymentContextMixin,
20
- TemplateView
21
- ):
22
- """Test view for development and debugging purposes."""
23
-
24
- template_name = 'payments/test.html'
25
- page_title = 'Payment System Test'
26
-
27
- def get_breadcrumbs(self):
28
- return [
29
- {'name': 'Dashboard', 'url': '/payments/admin/'},
30
- {'name': 'System Test', 'url': ''},
31
- ]
32
-
33
- def get_context_data(self, **kwargs):
34
- context = super().get_context_data(**kwargs)
35
-
36
- # Log access for audit
37
- log_view_access('payment_test', self.request.user)
38
-
39
- # Create sample data for testing templates
40
- sample_data = self._generate_sample_data()
41
-
42
- # Get system information
43
- system_info = self._get_system_info()
44
-
45
- # Get test scenarios
46
- test_scenarios = self._get_test_scenarios()
47
-
48
- # Get common context
49
- common_context = self.get_common_context()
50
-
51
- context.update({
52
- 'sample_data': sample_data,
53
- 'system_info': system_info,
54
- 'test_scenarios': test_scenarios,
55
- 'test_mode': True,
56
- **common_context
57
- })
58
-
59
- return context
60
-
61
- def _generate_sample_data(self):
62
- """Generate sample payment data for testing."""
63
- sample_payments = []
64
- statuses = ['pending', 'confirming', 'completed', 'failed']
65
- providers = ['nowpayments', 'cryptapi', 'cryptomus', 'stripe']
66
-
67
- for i in range(12):
68
- sample_payments.append({
69
- 'id': f'sample-{i}',
70
- 'internal_payment_id': f'PAY-{1000 + i}',
71
- 'provider_payment_id': f'PROV-{2000 + i}',
72
- 'amount_usd': 50.0 + (i * 25),
73
- 'currency_code': 'USD',
74
- 'status': statuses[i % len(statuses)],
75
- 'provider': providers[i % len(providers)],
76
- 'user_email': f'user{i}@example.com',
77
- 'created_at': timezone.now() - timedelta(hours=i),
78
- 'updated_at': timezone.now() - timedelta(minutes=i * 10),
79
- 'pay_address': f'1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa{i}' if i % 2 == 0 else None,
80
- 'pay_amount': 0.001 + (i * 0.0001) if i % 2 == 0 else None,
81
- })
82
-
83
- return {
84
- 'payments': sample_payments,
85
- 'stats': {
86
- 'total_count': len(sample_payments),
87
- 'pending_count': len([p for p in sample_payments if p['status'] == 'pending']),
88
- 'completed_count': len([p for p in sample_payments if p['status'] == 'completed']),
89
- 'failed_count': len([p for p in sample_payments if p['status'] == 'failed']),
90
- 'total_volume': sum(p['amount_usd'] for p in sample_payments),
91
- }
92
- }
93
-
94
- def _get_system_info(self):
95
- """Get system information for debugging."""
96
- import django
97
- import sys
98
- from django.conf import settings
99
-
100
- info = {
101
- 'django_version': django.get_version(),
102
- 'python_version': sys.version,
103
- 'debug_mode': settings.DEBUG,
104
- 'database_engine': settings.DATABASES['default']['ENGINE'],
105
- 'installed_apps': len(settings.INSTALLED_APPS),
106
- 'timezone': str(settings.TIME_ZONE),
107
- 'language': settings.LANGUAGE_CODE,
108
- }
109
-
110
- # Add payment-specific info
111
- try:
112
- from ...models import UniversalPayment, PaymentEvent
113
- info.update({
114
- 'total_payments': UniversalPayment.objects.count(),
115
- 'total_events': PaymentEvent.objects.count(),
116
- 'providers_in_use': list(
117
- UniversalPayment.objects.values_list('provider', flat=True).distinct()
118
- ),
119
- })
120
- except Exception:
121
- info.update({
122
- 'total_payments': 'Unable to query',
123
- 'total_events': 'Unable to query',
124
- 'providers_in_use': [],
125
- })
126
-
127
- return info
128
-
129
- def _get_test_scenarios(self):
130
- """Get available test scenarios."""
131
- scenarios = [
132
- {
133
- 'name': 'Template Component Test',
134
- 'description': 'Test all payment template components with sample data',
135
- 'endpoint': '/payments/test/?test=components',
136
- 'available': True,
137
- },
138
- {
139
- 'name': 'Status Badge Test',
140
- 'description': 'Test payment status badges for all possible statuses',
141
- 'endpoint': '/payments/test/?test=status_badges',
142
- 'available': True,
143
- },
144
- {
145
- 'name': 'Progress Bar Test',
146
- 'description': 'Test payment progress bars with different percentages',
147
- 'endpoint': '/payments/test/?test=progress_bars',
148
- 'available': True,
149
- },
150
- {
151
- 'name': 'Provider Statistics Test',
152
- 'description': 'Test provider statistics with sample data',
153
- 'endpoint': '/payments/test/?test=provider_stats',
154
- 'available': True,
155
- },
156
- {
157
- 'name': 'Real-time Updates Test',
158
- 'description': 'Test WebSocket connections and real-time updates',
159
- 'endpoint': '/payments/test/?test=realtime',
160
- 'available': False, # Requires WebSocket setup
161
- },
162
- {
163
- 'name': 'QR Code Generation Test',
164
- 'description': 'Test QR code generation for crypto payments',
165
- 'endpoint': '/payments/test/?test=qr_codes',
166
- 'available': True,
167
- },
168
- {
169
- 'name': 'API Integration Test',
170
- 'description': 'Test payment provider API integrations',
171
- 'endpoint': '/payments/test/?test=api_integration',
172
- 'available': False, # Requires API keys
173
- },
174
- ]
175
-
176
- # Add current test parameter
177
- current_test = self.request.GET.get('test', 'overview')
178
- for scenario in scenarios:
179
- scenario['is_current'] = scenario['endpoint'].endswith(f'test={current_test}')
180
-
181
- return scenarios
@@ -1,266 +0,0 @@
1
- """
2
- Webhook processing views with signature validation.
3
- """
4
-
5
- import json
6
- from django_cfg.modules.django_logger import get_logger
7
- from typing import Dict, Any
8
-
9
- from django.http import JsonResponse, HttpResponse
10
- from django.views.decorators.csrf import csrf_exempt
11
- from django.views.decorators.http import require_http_methods
12
- from django.utils.decorators import method_decorator
13
- from rest_framework.decorators import api_view, permission_classes
14
- from rest_framework.permissions import AllowAny
15
- from rest_framework.response import Response
16
- from rest_framework import status
17
-
18
- from ..services.core.payment_service import PaymentService
19
- from ..tasks.webhook_processing import process_webhook_with_fallback
20
- from ..services.security.webhook_validator import webhook_validator
21
- from ..services.security.error_handler import error_handler, SecurityError, ValidationError
22
-
23
- logger = get_logger("webhook_views")
24
-
25
-
26
- @csrf_exempt
27
- @require_http_methods(["POST"])
28
- def webhook_handler(request, provider: str):
29
- """
30
- Main webhook handler with signature validation.
31
-
32
- Accepts webhooks from payment providers and processes them
33
- with proper validation and fallback mechanisms.
34
- """
35
- try:
36
- # Parse webhook data
37
- webhook_data = json.loads(request.body.decode('utf-8'))
38
-
39
- # Extract request headers
40
- request_headers = {
41
- key: value for key, value in request.META.items()
42
- if key.startswith('HTTP_')
43
- }
44
-
45
- # Generate idempotency key for deduplication
46
- idempotency_key = _generate_idempotency_key(provider, webhook_data, request_headers)
47
-
48
- logger.info(f"๐Ÿ“ฅ Received webhook from {provider}, key: {idempotency_key}")
49
-
50
- # Validate webhook with enhanced security
51
- is_valid, validation_error = webhook_validator.validate_webhook(
52
- provider=provider,
53
- webhook_data=webhook_data,
54
- request_headers=request_headers,
55
- raw_body=request.body
56
- )
57
-
58
- if not is_valid:
59
- security_error = SecurityError(
60
- f"Webhook validation failed: {validation_error}",
61
- details={'provider': provider, 'validation_error': validation_error}
62
- )
63
- error_handler.handle_error(security_error, {
64
- 'provider': provider,
65
- 'webhook_data_keys': list(webhook_data.keys()),
66
- 'headers_count': len(request_headers)
67
- }, request)
68
-
69
- return JsonResponse(
70
- {'error': 'Webhook validation failed', 'code': 'INVALID_WEBHOOK'},
71
- status=403
72
- )
73
-
74
- # Process webhook (async with fallback to sync)
75
- result = process_webhook_with_fallback(
76
- provider=provider,
77
- webhook_data=webhook_data,
78
- idempotency_key=idempotency_key,
79
- request_headers=request_headers
80
- )
81
-
82
- if result.get('success'):
83
- logger.info(f"โœ… Webhook processed successfully: {idempotency_key}")
84
- return JsonResponse({
85
- 'status': 'success',
86
- 'idempotency_key': idempotency_key,
87
- 'processing_mode': result.get('mode', 'unknown')
88
- })
89
- else:
90
- logger.error(f"โŒ Webhook processing failed: {result.get('error')}")
91
- return JsonResponse({
92
- 'status': 'error',
93
- 'error': result.get('error', 'Processing failed'),
94
- 'idempotency_key': idempotency_key
95
- }, status=400)
96
-
97
- except json.JSONDecodeError as e:
98
- validation_error = ValidationError(
99
- f"Invalid JSON in webhook from {provider}",
100
- details={'provider': provider, 'json_error': str(e)}
101
- )
102
- error_result = error_handler.handle_error(validation_error, {
103
- 'provider': provider,
104
- 'raw_body_length': len(request.body) if request.body else 0
105
- }, request)
106
-
107
- return JsonResponse({
108
- 'error': 'Invalid JSON',
109
- 'code': validation_error.error_code
110
- }, status=400)
111
-
112
- except Exception as e:
113
- # Handle unexpected errors with centralized error handler
114
- error_result = error_handler.handle_error(e, {
115
- 'provider': provider,
116
- 'operation': 'webhook_processing',
117
- 'webhook_data_available': 'webhook_data' in locals()
118
- }, request)
119
-
120
- return JsonResponse({
121
- 'error': 'Internal server error',
122
- 'code': error_result.error.error_code
123
- }, status=500)
124
-
125
-
126
- @api_view(['POST'])
127
- @permission_classes([AllowAny])
128
- def webhook_test(request):
129
- """
130
- Test webhook endpoint for development.
131
-
132
- Allows testing webhook processing without requiring
133
- actual payment provider signatures.
134
- """
135
- try:
136
- provider = request.data.get('provider', 'test')
137
- webhook_data = request.data.get('webhook_data', {})
138
-
139
- # Add test marker
140
- webhook_data['_test_webhook'] = True
141
-
142
- # Generate test idempotency key
143
- import uuid
144
- idempotency_key = f"test_{uuid.uuid4().hex[:8]}"
145
-
146
- logger.info(f"๐Ÿงช Processing test webhook: {provider}")
147
-
148
- # Process with PaymentService directly (sync)
149
- payment_service = PaymentService()
150
- result = payment_service.process_webhook(
151
- provider=provider,
152
- webhook_data=webhook_data,
153
- request_headers={'HTTP_X_TEST': 'true'}
154
- )
155
-
156
- return Response({
157
- 'status': 'success',
158
- 'test_mode': True,
159
- 'provider': provider,
160
- 'idempotency_key': idempotency_key,
161
- 'result': result.dict() if hasattr(result, 'dict') else result
162
- })
163
-
164
- except Exception as e:
165
- logger.error(f"โŒ Test webhook error: {e}")
166
- return Response({
167
- 'status': 'error',
168
- 'error': str(e),
169
- 'test_mode': True
170
- }, status=status.HTTP_400_BAD_REQUEST)
171
-
172
-
173
- def _validate_webhook_signature(provider: str, webhook_data: Dict[str, Any],
174
- request_headers: Dict[str, str]) -> bool:
175
- """
176
- Validate webhook signature based on provider.
177
-
178
- Each provider has different signature validation methods.
179
- """
180
- try:
181
- if provider == 'nowpayments':
182
- return _validate_nowpayments_signature(webhook_data, request_headers)
183
- elif provider == 'cryptapi':
184
- return _validate_cryptapi_signature(webhook_data, request_headers)
185
- elif provider == 'test':
186
- return True # Allow test webhooks
187
- else:
188
- logger.warning(f"Unknown provider for signature validation: {provider}")
189
- return False
190
-
191
- except Exception as e:
192
- logger.error(f"Signature validation error for {provider}: {e}")
193
- return False
194
-
195
-
196
- def _validate_nowpayments_signature(webhook_data: Dict[str, Any],
197
- request_headers: Dict[str, str]) -> bool:
198
- """Validate NowPayments webhook signature."""
199
- import hmac
200
- import hashlib
201
- from ..utils.config_utils import get_payments_config
202
-
203
- # Get IPN secret from config
204
- config = get_payments_config()
205
- if not config or not hasattr(config, 'providers') or 'nowpayments' not in config.providers:
206
- logger.warning("NowPayments IPN secret not configured, skipping validation")
207
- return True # Allow if not configured (development mode)
208
-
209
- nowpayments_config = config.providers['nowpayments']
210
- ipn_secret = getattr(nowpayments_config, 'ipn_secret', None)
211
-
212
- if not ipn_secret:
213
- logger.warning("NowPayments IPN secret not configured, skipping validation")
214
- return True
215
-
216
- # Get signature from headers
217
- signature = request_headers.get('HTTP_X_NOWPAYMENTS_SIG')
218
- if not signature:
219
- logger.warning("No NowPayments signature found in headers")
220
- return False
221
-
222
- # Calculate expected signature
223
- payload = json.dumps(webhook_data, separators=(',', ':'), sort_keys=True)
224
- expected_signature = hmac.new(
225
- ipn_secret.encode(),
226
- payload.encode(),
227
- hashlib.sha512
228
- ).hexdigest()
229
-
230
- return hmac.compare_digest(signature, expected_signature)
231
-
232
-
233
- def _validate_cryptapi_signature(webhook_data: Dict[str, Any],
234
- request_headers: Dict[str, str]) -> bool:
235
- """Validate CryptAPI webhook signature."""
236
- # CryptAPI uses different validation method
237
- # For now, implement basic validation
238
-
239
- # Check if required fields are present
240
- required_fields = ['address_in', 'address_out', 'txid_in', 'value_coin', 'coin', 'confirmations']
241
- for field in required_fields:
242
- if field not in webhook_data:
243
- logger.warning(f"Missing required field in CryptAPI webhook: {field}")
244
- return False
245
-
246
- return True
247
-
248
-
249
- def _generate_idempotency_key(provider: str, webhook_data: Dict[str, Any],
250
- request_headers: Dict[str, str]) -> str:
251
- """Generate idempotency key for webhook deduplication."""
252
- import hashlib
253
-
254
- # Use provider + payment ID + timestamp for uniqueness
255
- payment_id = (
256
- webhook_data.get('payment_id') or
257
- webhook_data.get('order_id') or
258
- webhook_data.get('id') or
259
- 'unknown'
260
- )
261
-
262
- timestamp = webhook_data.get('created_at') or webhook_data.get('timestamp')
263
-
264
- # Create hash from key components
265
- key_data = f"{provider}:{payment_id}:{timestamp}"
266
- return hashlib.md5(key_data.encode()).hexdigest()[:16]