django-cfg 1.2.31__py3-none-any.whl โ†’ 1.3.3__py3-none-any.whl

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