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
@@ -1,312 +0,0 @@
1
- """
2
- AJAX endpoints for payment dashboard.
3
-
4
- Provides real-time data updates and interactive functionality.
5
- """
6
-
7
- from django.http import JsonResponse
8
- from django.shortcuts import get_object_or_404
9
- from django.utils import timezone
10
- from .base import superuser_required, get_progress_percentage, log_view_access
11
- from ...models import UniversalPayment, PaymentEvent
12
-
13
-
14
- @superuser_required
15
- def payment_status_ajax(request, payment_id):
16
- """AJAX endpoint for real-time payment status."""
17
- try:
18
- payment = get_object_or_404(UniversalPayment, id=payment_id)
19
-
20
- # Log access for audit
21
- log_view_access('payment_status_ajax', request.user, payment_id=payment_id)
22
-
23
- data = {
24
- 'id': str(payment.id),
25
- 'status': payment.status,
26
- 'status_display': payment.get_status_display(),
27
- 'progress_percentage': get_progress_percentage(payment.status),
28
- 'amount_usd': float(payment.amount_usd),
29
- 'currency_code': payment.currency_code,
30
- 'provider': payment.provider,
31
- 'provider_display': payment.provider.title(),
32
- 'created_at': payment.created_at.isoformat(),
33
- 'updated_at': payment.updated_at.isoformat(),
34
- }
35
-
36
- # Add completion time if available
37
- if payment.completed_at:
38
- data['completed_at'] = payment.completed_at.isoformat()
39
-
40
- # Add crypto-specific data
41
- if payment.provider in ['nowpayments', 'cryptapi', 'cryptomus']:
42
- data.update({
43
- 'is_crypto': True,
44
- 'pay_address': payment.pay_address,
45
- 'pay_amount': str(payment.pay_amount) if payment.pay_amount else None,
46
- 'network': getattr(payment, 'network', None),
47
- 'confirmations': getattr(payment, 'confirmations_count', 0),
48
- })
49
- else:
50
- data['is_crypto'] = False
51
-
52
- return JsonResponse(data)
53
-
54
- except Exception as e:
55
- return JsonResponse({'error': str(e)}, status=400)
56
-
57
-
58
- @superuser_required
59
- def payment_events_ajax(request, payment_id):
60
- """AJAX endpoint for payment events."""
61
- try:
62
- payment = get_object_or_404(UniversalPayment, id=payment_id)
63
-
64
- # Log access for audit
65
- log_view_access('payment_events_ajax', request.user, payment_id=payment_id)
66
-
67
- events = PaymentEvent.objects.filter(payment=payment).order_by('-created_at')
68
-
69
- events_data = []
70
- for event in events:
71
- event_data = {
72
- 'id': event.id,
73
- 'event_type': event.event_type,
74
- 'created_at': event.created_at.isoformat(),
75
- 'metadata': event.metadata or {},
76
- }
77
-
78
- # Add human-readable description
79
- event_data['description'] = _get_event_description(event)
80
-
81
- events_data.append(event_data)
82
-
83
- return JsonResponse({
84
- 'events': events_data,
85
- 'count': len(events_data)
86
- })
87
-
88
- except Exception as e:
89
- return JsonResponse({'error': str(e)}, status=400)
90
-
91
-
92
- @superuser_required
93
- def payment_stats_ajax(request):
94
- """AJAX endpoint for dashboard statistics."""
95
- try:
96
- # Log access for audit
97
- log_view_access('payment_stats_ajax', request.user)
98
-
99
- from .base import PaymentStatsMixin
100
-
101
- # Create instance to use mixin methods
102
- stats_mixin = PaymentStatsMixin()
103
-
104
- # Get basic stats
105
- payment_stats = stats_mixin.get_payment_stats()
106
- provider_stats = stats_mixin.get_provider_stats()
107
-
108
- # Get time range stats if requested
109
- days = int(request.GET.get('days', 30))
110
- time_range_stats = stats_mixin.get_time_range_stats(days)
111
-
112
- data = {
113
- 'payment_stats': payment_stats,
114
- 'provider_stats': list(provider_stats),
115
- 'time_range_stats': time_range_stats,
116
- 'last_updated': timezone.now().isoformat(),
117
- }
118
-
119
- return JsonResponse(data)
120
-
121
- except Exception as e:
122
- return JsonResponse({'error': str(e)}, status=500)
123
-
124
-
125
- @superuser_required
126
- def payment_search_ajax(request):
127
- """AJAX endpoint for payment search."""
128
- try:
129
- query = request.GET.get('q', '').strip()
130
- if not query:
131
- return JsonResponse({'results': []})
132
-
133
- # Log access for audit
134
- log_view_access('payment_search_ajax', request.user, query=query)
135
-
136
- from django.db.models import Q
137
-
138
- # Search payments
139
- payments = UniversalPayment.objects.filter(
140
- Q(internal_payment_id__icontains=query) |
141
- Q(provider_payment_id__icontains=query) |
142
- Q(user__email__icontains=query) |
143
- Q(pay_address__icontains=query)
144
- ).order_by('-created_at')[:20]
145
-
146
- results = []
147
- for payment in payments:
148
- results.append({
149
- 'id': str(payment.id),
150
- 'internal_id': payment.internal_payment_id,
151
- 'provider_id': payment.provider_payment_id,
152
- 'amount_usd': float(payment.amount_usd),
153
- 'currency_code': payment.currency_code,
154
- 'status': payment.status,
155
- 'status_display': payment.get_status_display(),
156
- 'provider': payment.provider,
157
- 'provider_display': payment.provider.title(),
158
- 'user_email': payment.user.email,
159
- 'created_at': payment.created_at.isoformat(),
160
- 'url': f'/payments/payment/{payment.id}/',
161
- })
162
-
163
- return JsonResponse({
164
- 'results': results,
165
- 'count': len(results),
166
- 'query': query
167
- })
168
-
169
- except Exception as e:
170
- return JsonResponse({'error': str(e)}, status=500)
171
-
172
-
173
- @superuser_required
174
- def payment_action_ajax(request, payment_id):
175
- """AJAX endpoint for payment actions (cancel, retry, etc.)."""
176
- try:
177
- if request.method != 'POST':
178
- return JsonResponse({'error': 'POST method required'}, status=405)
179
-
180
- payment = get_object_or_404(UniversalPayment, id=payment_id)
181
- action = request.POST.get('action')
182
-
183
- # Log access for audit
184
- log_view_access('payment_action_ajax', request.user,
185
- payment_id=payment_id, action=action)
186
-
187
- if action == 'cancel':
188
- return _handle_cancel_payment(payment, request.user)
189
- elif action == 'retry':
190
- return _handle_retry_payment(payment, request.user)
191
- elif action == 'refresh':
192
- return _handle_refresh_payment(payment, request.user)
193
- else:
194
- return JsonResponse({'error': 'Invalid action'}, status=400)
195
-
196
- except Exception as e:
197
- return JsonResponse({'error': str(e)}, status=500)
198
-
199
-
200
- def _get_event_description(event):
201
- """Get human-readable description for payment event."""
202
- descriptions = {
203
- 'created': 'Payment was created',
204
- 'pending': 'Payment is pending confirmation',
205
- 'confirming': 'Payment is being confirmed',
206
- 'confirmed': 'Payment has been confirmed',
207
- 'completed': 'Payment was completed successfully',
208
- 'failed': 'Payment failed',
209
- 'cancelled': 'Payment was cancelled',
210
- 'expired': 'Payment expired',
211
- 'refunded': 'Payment was refunded',
212
- 'webhook_received': 'Webhook notification received',
213
- 'status_updated': 'Payment status was updated',
214
- }
215
-
216
- description = descriptions.get(event.event_type, f'Event: {event.event_type}')
217
-
218
- # Add metadata details if available
219
- if event.metadata:
220
- if 'reason' in event.metadata:
221
- description += f" - {event.metadata['reason']}"
222
- if 'amount' in event.metadata:
223
- description += f" (Amount: ${event.metadata['amount']})"
224
-
225
- return description
226
-
227
-
228
- def _handle_cancel_payment(payment, user):
229
- """Handle payment cancellation."""
230
- if payment.status not in ['pending', 'confirming']:
231
- return JsonResponse({
232
- 'error': 'Payment cannot be cancelled in current status'
233
- }, status=400)
234
-
235
- try:
236
- # Update payment status
237
- payment.status = 'cancelled'
238
- payment.save()
239
-
240
- # Create event
241
- PaymentEvent.objects.create(
242
- payment=payment,
243
- event_type='cancelled',
244
- metadata={'cancelled_by': user.email}
245
- )
246
-
247
- return JsonResponse({
248
- 'success': True,
249
- 'message': 'Payment cancelled successfully',
250
- 'new_status': payment.status
251
- })
252
-
253
- except Exception as e:
254
- return JsonResponse({'error': f'Failed to cancel payment: {str(e)}'}, status=500)
255
-
256
-
257
- def _handle_retry_payment(payment, user):
258
- """Handle payment retry."""
259
- if payment.status not in ['failed', 'expired']:
260
- return JsonResponse({
261
- 'error': 'Payment cannot be retried in current status'
262
- }, status=400)
263
-
264
- try:
265
- # Reset payment status
266
- payment.status = 'pending'
267
- payment.save()
268
-
269
- # Create event
270
- PaymentEvent.objects.create(
271
- payment=payment,
272
- event_type='retried',
273
- metadata={'retried_by': user.email}
274
- )
275
-
276
- return JsonResponse({
277
- 'success': True,
278
- 'message': 'Payment retry initiated',
279
- 'new_status': payment.status
280
- })
281
-
282
- except Exception as e:
283
- return JsonResponse({'error': f'Failed to retry payment: {str(e)}'}, status=500)
284
-
285
-
286
- def _handle_refresh_payment(payment, user):
287
- """Handle payment status refresh."""
288
- try:
289
- # Try to refresh payment status from provider
290
- from ...services.core.payment_service import PaymentService
291
-
292
- payment_service = PaymentService()
293
- updated_payment = payment_service.refresh_payment_status(payment.id)
294
-
295
- # Create event
296
- PaymentEvent.objects.create(
297
- payment=payment,
298
- event_type='refreshed',
299
- metadata={'refreshed_by': user.email}
300
- )
301
-
302
- return JsonResponse({
303
- 'success': True,
304
- 'message': 'Payment status refreshed',
305
- 'new_status': updated_payment.status
306
- })
307
-
308
- except Exception as e:
309
- return JsonResponse({
310
- 'success': False,
311
- 'message': f'Failed to refresh payment: {str(e)}'
312
- })
@@ -1,204 +0,0 @@
1
- """
2
- Base mixins and decorators for payment template views.
3
-
4
- Provides security and common functionality for all payment dashboard views.
5
- """
6
-
7
- from django.contrib.auth.decorators import user_passes_test
8
- from django.utils.decorators import method_decorator
9
- from django.db.models import Q, Count, Sum
10
- from django.utils import timezone
11
- from datetime import timedelta
12
- import logging
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- def superuser_required(function=None):
18
- """Decorator that checks if user is superuser."""
19
- actual_decorator = user_passes_test(
20
- lambda u: u.is_authenticated and u.is_superuser,
21
- login_url='/admin/login/'
22
- )
23
- if function:
24
- return actual_decorator(function)
25
- return actual_decorator
26
-
27
-
28
- class SuperuserRequiredMixin:
29
- """Mixin that requires superuser access for all views."""
30
-
31
- @method_decorator(superuser_required)
32
- def dispatch(self, request, *args, **kwargs):
33
- return super().dispatch(request, *args, **kwargs)
34
-
35
-
36
- class PaymentFilterMixin:
37
- """Mixin that provides common payment filtering functionality."""
38
-
39
- def get_filtered_payments(self, request=None):
40
- """Get payments with common filters applied."""
41
- if request is None:
42
- request = self.request
43
-
44
- from ...models import UniversalPayment
45
-
46
- # Base queryset
47
- payments_qs = UniversalPayment.objects.select_related('user')
48
-
49
- # Apply filters from GET parameters
50
- status_filter = request.GET.get('status', '')
51
- provider_filter = request.GET.get('provider', '')
52
- search_query = request.GET.get('search', '')
53
- date_filter = request.GET.get('date', '')
54
-
55
- if status_filter:
56
- payments_qs = payments_qs.filter(status=status_filter)
57
- if provider_filter:
58
- payments_qs = payments_qs.filter(provider=provider_filter)
59
- if search_query:
60
- payments_qs = payments_qs.filter(
61
- Q(internal_payment_id__icontains=search_query) |
62
- Q(provider_payment_id__icontains=search_query) |
63
- Q(amount_usd__icontains=search_query) |
64
- Q(user__email__icontains=search_query)
65
- )
66
- if date_filter:
67
- try:
68
- filter_date = timezone.datetime.strptime(date_filter, '%Y-%m-%d').date()
69
- payments_qs = payments_qs.filter(created_at__date=filter_date)
70
- except ValueError:
71
- pass # Invalid date format, ignore filter
72
-
73
- return payments_qs
74
-
75
- def get_filter_context(self, request=None):
76
- """Get filter values for template context."""
77
- if request is None:
78
- request = self.request
79
-
80
- return {
81
- 'status': request.GET.get('status', ''),
82
- 'provider': request.GET.get('provider', ''),
83
- 'search': request.GET.get('search', ''),
84
- 'date': request.GET.get('date', ''),
85
- }
86
-
87
-
88
- class PaymentStatsMixin:
89
- """Mixin that provides payment statistics functionality."""
90
-
91
- def get_payment_stats(self, queryset=None):
92
- """Get payment statistics from queryset or all payments."""
93
- if queryset is None:
94
- from ...models import UniversalPayment
95
- queryset = UniversalPayment.objects.all()
96
-
97
- stats = queryset.aggregate(
98
- total_count=Count('id'),
99
- pending_count=Count('id', filter=Q(status='pending')),
100
- confirming_count=Count('id', filter=Q(status='confirming')),
101
- completed_count=Count('id', filter=Q(status='completed')),
102
- failed_count=Count('id', filter=Q(status='failed')),
103
- total_volume=Sum('amount_usd')
104
- )
105
-
106
- # Convert Decimal to float for JSON serialization
107
- if stats['total_volume']:
108
- stats['total_volume'] = float(stats['total_volume'])
109
- else:
110
- stats['total_volume'] = 0.0
111
-
112
- return stats
113
-
114
- def get_provider_stats(self, queryset=None):
115
- """Get provider-specific statistics."""
116
- if queryset is None:
117
- from ...models import UniversalPayment
118
- queryset = UniversalPayment.objects.all()
119
-
120
- provider_stats = queryset.values('provider').annotate(
121
- count=Count('id'),
122
- volume=Sum('amount_usd'),
123
- completed_count=Count('id', filter=Q(status='completed')),
124
- ).order_by('-volume')
125
-
126
- # Calculate success rate
127
- for stat in provider_stats:
128
- if stat['count'] > 0:
129
- stat['success_rate'] = (stat['completed_count'] / stat['count']) * 100
130
- else:
131
- stat['success_rate'] = 0
132
-
133
- # Convert Decimal to float
134
- if stat['volume']:
135
- stat['volume'] = float(stat['volume'])
136
- else:
137
- stat['volume'] = 0.0
138
-
139
- return provider_stats
140
-
141
- def get_time_range_stats(self, days=30):
142
- """Get statistics for a specific time range."""
143
- from ...models import UniversalPayment
144
-
145
- end_date = timezone.now()
146
- start_date = end_date - timedelta(days=days)
147
-
148
- queryset = UniversalPayment.objects.filter(
149
- created_at__gte=start_date,
150
- created_at__lte=end_date
151
- )
152
-
153
- return self.get_payment_stats(queryset)
154
-
155
-
156
- class PaymentContextMixin:
157
- """Mixin that provides common context data for payment views."""
158
-
159
- def get_common_context(self):
160
- """Get common context data used across multiple views."""
161
- from ...models import PaymentEvent
162
-
163
- # Get recent events for activity feed
164
- recent_events = PaymentEvent.objects.select_related('payment').order_by('-created_at')[:10]
165
-
166
- return {
167
- 'recent_events': recent_events,
168
- 'page_title': self.get_page_title(),
169
- 'breadcrumbs': self.get_breadcrumbs(),
170
- }
171
-
172
- def get_page_title(self):
173
- """Get page title for the view."""
174
- return getattr(self, 'page_title', 'Payment Dashboard')
175
-
176
- def get_breadcrumbs(self):
177
- """Get breadcrumb navigation for the view."""
178
- return getattr(self, 'breadcrumbs', [
179
- {'name': 'Payment Dashboard', 'url': '/payments/admin/'},
180
- ])
181
-
182
-
183
- def get_progress_percentage(status):
184
- """Helper function to calculate progress percentage."""
185
- progress_map = {
186
- 'pending': 10,
187
- 'confirming': 40,
188
- 'confirmed': 70,
189
- 'completed': 100,
190
- 'failed': 0,
191
- 'expired': 0,
192
- 'cancelled': 0,
193
- 'refunded': 50,
194
- }
195
- return progress_map.get(status, 0)
196
-
197
-
198
- def log_view_access(view_name, user, **kwargs):
199
- """Log access to payment views for audit purposes."""
200
- extra_info = ', '.join([f"{k}={v}" for k, v in kwargs.items()])
201
- logger.info(
202
- f"Payment dashboard access: {view_name} by {user.email} "
203
- f"(superuser={user.is_superuser}) {extra_info}"
204
- )
@@ -1,60 +0,0 @@
1
- """
2
- Main payment dashboard view.
3
-
4
- Provides overview, statistics, and recent payments for superuser access.
5
- """
6
-
7
- from django.views.generic import TemplateView
8
- from .base import (
9
- SuperuserRequiredMixin,
10
- PaymentFilterMixin,
11
- PaymentStatsMixin,
12
- PaymentContextMixin,
13
- log_view_access
14
- )
15
-
16
-
17
- class PaymentDashboardView(
18
- SuperuserRequiredMixin,
19
- PaymentFilterMixin,
20
- PaymentStatsMixin,
21
- PaymentContextMixin,
22
- TemplateView
23
- ):
24
- """Main payment dashboard with overview and recent payments."""
25
-
26
- template_name = 'payments/dashboard.html'
27
- page_title = 'Payment Dashboard'
28
-
29
- def get_context_data(self, **kwargs):
30
- context = super().get_context_data(**kwargs)
31
-
32
- # Log access for audit
33
- log_view_access('dashboard', self.request.user)
34
-
35
- # Get filtered payments
36
- payments_qs = self.get_filtered_payments()
37
-
38
- # Get recent payments (limit to 20 for performance)
39
- recent_payments = payments_qs.order_by('-created_at')[:20]
40
-
41
- # Check if there are more payments for pagination
42
- has_more = payments_qs.count() > 20
43
-
44
- # Get statistics
45
- payment_stats = self.get_payment_stats()
46
- provider_stats = self.get_provider_stats()
47
-
48
- # Get common context
49
- common_context = self.get_common_context()
50
-
51
- context.update({
52
- 'payments': recent_payments,
53
- 'has_more_payments': has_more,
54
- 'payment_stats': payment_stats,
55
- 'provider_stats': provider_stats,
56
- 'filters': self.get_filter_context(),
57
- **common_context
58
- })
59
-
60
- return context
@@ -1,102 +0,0 @@
1
- """
2
- Payment detail view.
3
-
4
- Provides detailed information about a single payment for superuser access.
5
- """
6
-
7
- from django.views.generic import DetailView
8
- from .base import (
9
- SuperuserRequiredMixin,
10
- PaymentContextMixin,
11
- log_view_access
12
- )
13
- from ...models import UniversalPayment, PaymentEvent
14
-
15
-
16
- class PaymentDetailView(
17
- SuperuserRequiredMixin,
18
- PaymentContextMixin,
19
- DetailView
20
- ):
21
- """Detailed view for a single payment."""
22
-
23
- model = UniversalPayment
24
- template_name = 'payments/payment_detail.html'
25
- context_object_name = 'payment'
26
- page_title = 'Payment Details'
27
-
28
- def get_breadcrumbs(self):
29
- payment = self.get_object()
30
- return [
31
- {'name': 'Dashboard', 'url': '/payments/admin/'},
32
- {'name': 'Payments', 'url': '/payments/admin/list/'},
33
- {'name': f'Payment #{payment.internal_payment_id or str(payment.id)[:8]}', 'url': ''},
34
- ]
35
-
36
- def get_context_data(self, **kwargs):
37
- context = super().get_context_data(**kwargs)
38
- payment = self.get_object()
39
-
40
- # Log access for audit
41
- log_view_access('payment_detail', self.request.user, payment_id=payment.id)
42
-
43
- # Get payment events for this payment
44
- events = PaymentEvent.objects.filter(payment=payment).order_by('-created_at')
45
-
46
- # Get related payments (same user, similar amount range)
47
- related_payments = UniversalPayment.objects.filter(
48
- user=payment.user,
49
- amount_usd__gte=payment.amount_usd * 0.8,
50
- amount_usd__lte=payment.amount_usd * 1.2
51
- ).exclude(id=payment.id).order_by('-created_at')[:5]
52
-
53
- # Get provider-specific information
54
- provider_info = self._get_provider_info(payment)
55
-
56
- # Get common context
57
- common_context = self.get_common_context()
58
-
59
- context.update({
60
- 'events': events,
61
- 'related_payments': related_payments,
62
- 'provider_info': provider_info,
63
- 'can_retry': self._can_retry_payment(payment),
64
- 'can_cancel': self._can_cancel_payment(payment),
65
- 'can_refund': self._can_refund_payment(payment),
66
- **common_context
67
- })
68
-
69
- return context
70
-
71
- def _get_provider_info(self, payment):
72
- """Get provider-specific information for the payment."""
73
- info = {
74
- 'display_name': payment.provider.title(),
75
- 'is_crypto': payment.provider in ['nowpayments', 'cryptapi', 'cryptomus'],
76
- 'supports_qr': payment.provider in ['nowpayments', 'cryptapi', 'cryptomus'],
77
- 'supports_webhook': True,
78
- }
79
-
80
- # Add crypto-specific info
81
- if info['is_crypto'] and payment.pay_address:
82
- info.update({
83
- 'crypto_address': payment.pay_address,
84
- 'crypto_amount': payment.pay_amount,
85
- 'crypto_currency': payment.currency_code,
86
- 'network': getattr(payment, 'network', 'mainnet'),
87
- 'confirmations': getattr(payment, 'confirmations_count', 0),
88
- })
89
-
90
- return info
91
-
92
- def _can_retry_payment(self, payment):
93
- """Check if payment can be retried."""
94
- return payment.status in ['failed', 'expired']
95
-
96
- def _can_cancel_payment(self, payment):
97
- """Check if payment can be cancelled."""
98
- return payment.status in ['pending', 'confirming']
99
-
100
- def _can_refund_payment(self, payment):
101
- """Check if payment can be refunded."""
102
- return payment.status == 'completed'