django-cfg 1.3.1__py3-none-any.whl → 1.3.5__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 (115) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin_interface/old/payments/base.html +175 -0
  3. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +125 -0
  4. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +113 -0
  5. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +35 -0
  6. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +309 -0
  7. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +303 -0
  8. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +382 -0
  9. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +518 -0
  10. django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/components.css +248 -9
  11. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +163 -0
  12. django_cfg/apps/payments/admin_interface/serializers/__init__.py +39 -0
  13. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +149 -0
  14. django_cfg/apps/payments/admin_interface/serializers/webhook_serializers.py +114 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/base.html +55 -90
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/dialog.html +81 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_help_dialog.html +112 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_status.html +175 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +21 -17
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +123 -250
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +170 -269
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +152 -355
  23. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +202 -551
  24. django_cfg/apps/payments/admin_interface/views/__init__.py +25 -14
  25. django_cfg/apps/payments/admin_interface/views/api/__init__.py +20 -0
  26. django_cfg/apps/payments/admin_interface/views/api/payments.py +191 -0
  27. django_cfg/apps/payments/admin_interface/views/api/stats.py +206 -0
  28. django_cfg/apps/payments/admin_interface/views/api/users.py +60 -0
  29. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +257 -0
  30. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +70 -0
  31. django_cfg/apps/payments/admin_interface/views/base.py +114 -0
  32. django_cfg/apps/payments/admin_interface/views/dashboard.py +60 -0
  33. django_cfg/apps/payments/admin_interface/views/forms.py +94 -0
  34. django_cfg/apps/payments/config/helpers.py +2 -2
  35. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +429 -0
  36. django_cfg/apps/payments/management/commands/currency_stats.py +443 -0
  37. django_cfg/apps/payments/management/commands/manage_currencies.py +9 -20
  38. django_cfg/apps/payments/management/commands/manage_providers.py +5 -5
  39. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  40. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  41. django_cfg/apps/payments/middleware/api_access.py +35 -34
  42. django_cfg/apps/payments/migrations/0001_initial.py +1 -1
  43. django_cfg/apps/payments/models/balance.py +5 -2
  44. django_cfg/apps/payments/models/managers/api_key_managers.py +6 -2
  45. django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
  46. django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
  47. django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
  48. django_cfg/apps/payments/models/subscriptions.py +0 -24
  49. django_cfg/apps/payments/services/cache/__init__.py +1 -1
  50. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  51. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  52. django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
  53. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  54. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  55. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  56. django_cfg/apps/payments/services/core/balance_service.py +13 -2
  57. django_cfg/apps/payments/services/core/payment_service.py +49 -22
  58. django_cfg/apps/payments/services/integrations/ngrok_service.py +3 -3
  59. django_cfg/apps/payments/services/providers/registry.py +20 -0
  60. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  61. django_cfg/apps/payments/signals/balance_signals.py +8 -5
  62. django_cfg/apps/payments/static/payments/js/api-client.js +385 -0
  63. django_cfg/apps/payments/static/payments/js/ngrok-status.js +58 -0
  64. django_cfg/apps/payments/static/payments/js/payment-dashboard.js +50 -0
  65. django_cfg/apps/payments/static/payments/js/payment-form.js +175 -0
  66. django_cfg/apps/payments/static/payments/js/payment-list.js +95 -0
  67. django_cfg/apps/payments/static/payments/js/webhook-dashboard.js +154 -0
  68. django_cfg/apps/payments/urls.py +4 -0
  69. django_cfg/apps/payments/urls_admin.py +37 -18
  70. django_cfg/apps/payments/views/api/api_keys.py +14 -0
  71. django_cfg/apps/payments/views/api/base.py +1 -0
  72. django_cfg/apps/payments/views/api/currencies.py +2 -2
  73. django_cfg/apps/payments/views/api/payments.py +11 -5
  74. django_cfg/apps/payments/views/api/subscriptions.py +36 -31
  75. django_cfg/apps/payments/views/overview/__init__.py +40 -0
  76. django_cfg/apps/payments/views/overview/serializers.py +205 -0
  77. django_cfg/apps/payments/views/overview/services.py +439 -0
  78. django_cfg/apps/payments/views/overview/urls.py +27 -0
  79. django_cfg/apps/payments/views/overview/views.py +231 -0
  80. django_cfg/apps/payments/views/serializers/api_keys.py +20 -6
  81. django_cfg/apps/payments/views/serializers/balances.py +5 -8
  82. django_cfg/apps/payments/views/serializers/currencies.py +2 -6
  83. django_cfg/apps/payments/views/serializers/payments.py +37 -32
  84. django_cfg/apps/payments/views/serializers/subscriptions.py +4 -26
  85. django_cfg/apps/urls.py +2 -1
  86. django_cfg/core/config.py +25 -15
  87. django_cfg/core/generation.py +12 -12
  88. django_cfg/core/integration/display/startup.py +1 -1
  89. django_cfg/core/validation.py +4 -4
  90. django_cfg/management/commands/show_config.py +2 -2
  91. django_cfg/management/commands/tree.py +1 -3
  92. django_cfg/middleware/__init__.py +2 -0
  93. django_cfg/middleware/static_nocache.py +55 -0
  94. django_cfg/models/payments.py +13 -15
  95. django_cfg/models/security.py +15 -0
  96. django_cfg/modules/django_ngrok.py +6 -0
  97. django_cfg/modules/django_unfold/dashboard.py +1 -3
  98. django_cfg/utils/smart_defaults.py +51 -5
  99. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
  100. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/RECORD +111 -69
  101. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +0 -38
  102. django_cfg/apps/payments/admin_interface/views/payment_views.py +0 -259
  103. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +0 -37
  104. django_cfg/apps/payments/services/cache/cache_service.py +0 -235
  105. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/loading_spinner.html +0 -0
  106. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/notification.html +0 -0
  107. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/provider_card.html +0 -0
  108. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/currency_converter.html +0 -0
  109. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/payment_status.html +0 -0
  110. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/dashboard.css +0 -0
  111. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/components.js +0 -0
  112. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/utils.js +0 -0
  113. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
  114. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
  115. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,439 @@
1
+ """
2
+ 💰 Payments Overview Dashboard Services
3
+
4
+ Services for aggregating payments dashboard data from existing models.
5
+ """
6
+ from django.db.models import Count, Sum, Avg, Q, Max
7
+ from django.utils import timezone
8
+ from datetime import timedelta, date
9
+ from decimal import Decimal
10
+ from typing import Dict, List, Any, Optional
11
+
12
+ from ...models import (
13
+ UniversalPayment,
14
+ UserBalance,
15
+ Transaction,
16
+ Subscription,
17
+ EndpointGroup,
18
+ APIKey,
19
+ )
20
+
21
+
22
+ class PaymentsDashboardMetricsService:
23
+ """
24
+ Service for calculating payments dashboard metrics
25
+ """
26
+
27
+ def __init__(self, user):
28
+ self.user = user
29
+ self.now = timezone.now()
30
+ self.today = self.now.date()
31
+ self.month_start = self.today.replace(day=1)
32
+ self.last_month_start = (self.month_start - timedelta(days=1)).replace(day=1)
33
+ self.last_month_end = self.month_start - timedelta(days=1)
34
+
35
+ def get_dashboard_metrics(self) -> Dict[str, Any]:
36
+ """
37
+ Get complete payments dashboard metrics
38
+ """
39
+ return {
40
+ 'balance': self.get_balance_overview(),
41
+ 'subscription': self.get_subscription_overview(),
42
+ 'api_keys': self.get_api_keys_overview(),
43
+ 'payments': self.get_payments_overview(),
44
+ }
45
+
46
+ def get_balance_overview(self) -> Dict[str, Any]:
47
+ """
48
+ Get user balance overview using manager methods
49
+ """
50
+ balance = UserBalance.objects.get_or_create_for_user(self.user)
51
+
52
+ return {
53
+ 'current_balance': balance.balance_usd,
54
+ 'balance_display': balance.balance_display,
55
+ 'total_deposited': balance.total_deposited,
56
+ 'total_spent': balance.total_spent,
57
+ 'last_transaction_at': balance.last_transaction_at,
58
+ 'has_transactions': balance.has_transactions,
59
+ 'is_empty': balance.is_empty,
60
+ }
61
+
62
+ def get_subscription_overview(self) -> Optional[Dict[str, Any]]:
63
+ """
64
+ Get current subscription overview using manager methods
65
+ """
66
+ subscription = Subscription.objects.get_active_for_user(self.user)
67
+
68
+ if not subscription:
69
+ return None
70
+
71
+ # Get endpoint groups info
72
+ endpoint_groups = subscription.endpoint_groups.filter(is_enabled=True)
73
+ endpoint_groups_names = list(endpoint_groups.values_list('name', flat=True))
74
+
75
+ return {
76
+ 'tier': subscription.tier,
77
+ 'tier_display': subscription.tier_display,
78
+ 'status': subscription.status,
79
+ 'status_display': subscription.get_status_display(),
80
+ 'status_color': subscription.status_color,
81
+ 'is_active': subscription.is_active,
82
+ 'is_expired': subscription.is_expired,
83
+ 'days_remaining': subscription.days_remaining,
84
+
85
+ # Limits and usage
86
+ 'requests_per_hour': subscription.requests_per_hour,
87
+ 'requests_per_day': subscription.requests_per_day,
88
+ 'total_requests': subscription.total_requests,
89
+ 'usage_percentage': subscription.usage_percentage,
90
+
91
+ # Billing
92
+ 'monthly_cost_usd': subscription.monthly_cost_usd,
93
+ 'cost_display': f"${subscription.monthly_cost_usd:.2f}/month",
94
+
95
+ # Dates
96
+ 'starts_at': subscription.starts_at,
97
+ 'expires_at': subscription.expires_at,
98
+ 'last_request_at': subscription.last_request_at,
99
+
100
+ # Access
101
+ 'endpoint_groups_count': endpoint_groups.count(),
102
+ 'endpoint_groups': endpoint_groups_names,
103
+ }
104
+
105
+ def get_api_keys_overview(self) -> Dict[str, Any]:
106
+ """
107
+ Get API keys overview using manager methods
108
+ """
109
+ api_keys = APIKey.objects.by_user(self.user)
110
+
111
+ total_keys = api_keys.count()
112
+ active_keys = api_keys.active().count()
113
+ expired_keys = api_keys.expired().count()
114
+
115
+ # Total requests across all keys
116
+ total_requests = api_keys.aggregate(
117
+ total=Sum('total_requests')
118
+ )['total'] or 0
119
+
120
+ # Last used timestamp
121
+ last_used_at = api_keys.aggregate(
122
+ last_used=Max('last_used_at')
123
+ )['last_used']
124
+
125
+ # Most used key
126
+ most_used_key = api_keys.order_by('-total_requests').first()
127
+ most_used_key_name = most_used_key.name if most_used_key else None
128
+ most_used_key_requests = most_used_key.total_requests if most_used_key else 0
129
+
130
+ # Keys expiring soon (within 7 days)
131
+ expiring_soon = api_keys.expiring_soon(7).count()
132
+
133
+ return {
134
+ 'total_keys': total_keys,
135
+ 'active_keys': active_keys,
136
+ 'expired_keys': expired_keys,
137
+ 'total_requests': total_requests,
138
+ 'last_used_at': last_used_at,
139
+ 'most_used_key_name': most_used_key_name,
140
+ 'most_used_key_requests': most_used_key_requests,
141
+ 'expiring_soon_count': expiring_soon,
142
+ }
143
+
144
+ def get_payments_overview(self) -> Dict[str, Any]:
145
+ """
146
+ Get payments overview using manager methods
147
+ """
148
+ payments = UniversalPayment.objects.by_user(self.user)
149
+
150
+ # Basic counts using manager methods
151
+ total_payments = payments.count()
152
+ completed_payments = payments.completed().count()
153
+ pending_payments = payments.pending().count()
154
+ failed_payments = payments.failed().count()
155
+
156
+ # Amount calculations
157
+ total_amount_usd = payments.total_amount()
158
+ completed_amount_usd = payments.completed().total_amount()
159
+ average_payment_usd = payments.average_amount()
160
+
161
+ # Success rate
162
+ success_rate = (completed_payments / total_payments * 100) if total_payments > 0 else 0.0
163
+
164
+ # Recent activity
165
+ last_payment = payments.order_by('-created_at').first()
166
+ last_payment_at = last_payment.created_at if last_payment else None
167
+
168
+ # This month stats using manager methods
169
+ payments_this_month = payments.this_month().count()
170
+ amount_this_month = payments.this_month().total_amount()
171
+
172
+ # Top currency
173
+ top_currency_data = payments.values('currency__code').annotate(
174
+ count=Count('id')
175
+ ).order_by('-count').first()
176
+
177
+ top_currency = top_currency_data['currency__code'] if top_currency_data else None
178
+ top_currency_count = top_currency_data['count'] if top_currency_data else 0
179
+
180
+ return {
181
+ 'total_payments': total_payments,
182
+ 'completed_payments': completed_payments,
183
+ 'pending_payments': pending_payments,
184
+ 'failed_payments': failed_payments,
185
+
186
+ 'total_amount_usd': total_amount_usd,
187
+ 'completed_amount_usd': completed_amount_usd,
188
+ 'average_payment_usd': average_payment_usd,
189
+
190
+ 'success_rate': round(success_rate, 2),
191
+
192
+ 'last_payment_at': last_payment_at,
193
+ 'payments_this_month': payments_this_month,
194
+ 'amount_this_month': amount_this_month,
195
+
196
+ 'top_currency': top_currency,
197
+ 'top_currency_count': top_currency_count,
198
+ }
199
+
200
+
201
+ class PaymentsUsageChartService:
202
+ """
203
+ Service for generating payments usage chart data
204
+ """
205
+
206
+ def __init__(self, user):
207
+ self.user = user
208
+
209
+ def get_chart_data(self, period: str = '30d') -> Dict[str, Any]:
210
+ """
211
+ Get chart data for payments analytics
212
+ """
213
+ days_map = {
214
+ '7d': 7,
215
+ '30d': 30,
216
+ '90d': 90,
217
+ '1y': 365
218
+ }
219
+
220
+ days = days_map.get(period, 30)
221
+ start_date = timezone.now().date() - timedelta(days=days)
222
+
223
+ # Get payments for the period using manager methods
224
+ payments = UniversalPayment.objects.by_user(self.user).filter(
225
+ created_at__date__gte=start_date
226
+ ).values('created_at__date').annotate(
227
+ total_amount=Sum('amount_usd'),
228
+ completed_amount=Sum(
229
+ 'actual_amount_usd',
230
+ filter=Q(status=UniversalPayment.PaymentStatus.COMPLETED)
231
+ ),
232
+ payment_count=Count('id'),
233
+ completed_count=Count(
234
+ 'id',
235
+ filter=Q(status=UniversalPayment.PaymentStatus.COMPLETED)
236
+ ),
237
+ failed_count=Count(
238
+ 'id',
239
+ filter=Q(status__in=[
240
+ UniversalPayment.PaymentStatus.FAILED,
241
+ UniversalPayment.PaymentStatus.EXPIRED,
242
+ UniversalPayment.PaymentStatus.CANCELLED
243
+ ])
244
+ )
245
+ ).order_by('created_at__date')
246
+
247
+ # Generate chart series data
248
+ amounts_data = []
249
+ completed_data = []
250
+ failed_data = []
251
+
252
+ # Create data points for each day
253
+ for i in range(days):
254
+ current_date = start_date + timedelta(days=i)
255
+ date_str = current_date.strftime('%Y-%m-%d')
256
+
257
+ # Find payments for this date
258
+ day_data = next(
259
+ (p for p in payments if p['created_at__date'] == current_date),
260
+ None
261
+ )
262
+
263
+ total_amount = day_data['total_amount'] if day_data else 0.0
264
+ completed_amount = day_data['completed_amount'] if day_data else 0.0
265
+ failed_count = day_data['failed_count'] if day_data else 0
266
+
267
+ amounts_data.append({'x': date_str, 'y': total_amount})
268
+ completed_data.append({'x': date_str, 'y': completed_amount})
269
+ failed_data.append({'x': date_str, 'y': failed_count})
270
+
271
+ # Calculate totals
272
+ total_amount = sum(p['y'] for p in amounts_data)
273
+ total_payments = sum(1 for p in amounts_data if p['y'] > 0)
274
+ completed_amount = sum(p['y'] for p in completed_data)
275
+ success_rate = (completed_amount / total_amount * 100) if total_amount > 0 else 0.0
276
+
277
+ return {
278
+ 'series': [
279
+ {
280
+ 'name': 'Total Amount',
281
+ 'data': amounts_data,
282
+ 'color': '#3B82F6'
283
+ },
284
+ {
285
+ 'name': 'Completed Amount',
286
+ 'data': completed_data,
287
+ 'color': '#10B981'
288
+ },
289
+ {
290
+ 'name': 'Failed Payments',
291
+ 'data': failed_data,
292
+ 'color': '#EF4444'
293
+ }
294
+ ],
295
+ 'period': period,
296
+ 'total_amount': total_amount,
297
+ 'total_payments': total_payments,
298
+ 'success_rate': round(success_rate, 2)
299
+ }
300
+
301
+
302
+ class RecentPaymentsService:
303
+ """
304
+ Service for getting recent payments and transactions
305
+ """
306
+
307
+ def __init__(self, user):
308
+ self.user = user
309
+
310
+ def get_recent_payments(self, limit: int = 10) -> List[Dict[str, Any]]:
311
+ """
312
+ Get recent payments for the user using manager methods
313
+ """
314
+ payments = UniversalPayment.objects.by_user(self.user).optimized().order_by('-created_at')[:limit]
315
+
316
+ result = []
317
+ for payment in payments:
318
+ result.append({
319
+ 'id': payment.id,
320
+ 'internal_payment_id': payment.internal_payment_id,
321
+ 'amount_usd': payment.amount_usd,
322
+ 'amount_display': payment.amount_display,
323
+ 'currency_code': payment.currency.code,
324
+ 'status': payment.status,
325
+ 'status_display': payment.get_status_display(),
326
+ 'status_color': payment.status_color,
327
+ 'provider': payment.provider,
328
+ 'created_at': payment.created_at,
329
+ 'completed_at': payment.completed_at,
330
+
331
+ # Status flags
332
+ 'is_pending': payment.is_pending,
333
+ 'is_completed': payment.is_completed,
334
+ 'is_failed': payment.is_failed,
335
+ })
336
+
337
+ return result
338
+
339
+ def get_recent_transactions(self, limit: int = 10) -> List[Dict[str, Any]]:
340
+ """
341
+ Get recent transactions for the user using manager methods
342
+ """
343
+ transactions = Transaction.objects.by_user(self.user).optimized().order_by('-created_at')[:limit]
344
+
345
+ result = []
346
+ for transaction in transactions:
347
+ result.append({
348
+ 'id': transaction.id,
349
+ 'transaction_type': transaction.transaction_type,
350
+ 'amount_usd': transaction.amount_usd,
351
+ 'amount_display': transaction.amount_display,
352
+ 'balance_after': transaction.balance_after,
353
+ 'description': transaction.description,
354
+ 'created_at': transaction.created_at,
355
+ 'payment_id': transaction.payment_id,
356
+
357
+ # Type info
358
+ 'is_credit': transaction.is_credit,
359
+ 'is_debit': transaction.is_debit,
360
+ 'type_color': transaction.type_color,
361
+ })
362
+
363
+ return result
364
+
365
+
366
+ class PaymentsAnalyticsService:
367
+ """
368
+ Service for payments analytics and insights
369
+ """
370
+
371
+ def __init__(self, user):
372
+ self.user = user
373
+
374
+ def get_payment_analytics(self, limit: int = 10) -> List[Dict[str, Any]]:
375
+ """
376
+ Get payment analytics by currency, provider, etc.
377
+ """
378
+ # Analytics by currency using manager methods
379
+ currency_stats = UniversalPayment.objects.by_user(self.user).values(
380
+ 'currency__code', 'currency__name'
381
+ ).annotate(
382
+ total_payments=Count('id'),
383
+ total_amount=Sum('amount_usd'),
384
+ completed_payments=Count(
385
+ 'id',
386
+ filter=Q(status=UniversalPayment.PaymentStatus.COMPLETED)
387
+ ),
388
+ avg_amount=Avg('amount_usd'),
389
+ success_rate=Count(
390
+ 'id',
391
+ filter=Q(status=UniversalPayment.PaymentStatus.COMPLETED)
392
+ ) * 100.0 / Count('id')
393
+ ).order_by('-total_amount')[:limit]
394
+
395
+ result = []
396
+ for stat in currency_stats:
397
+ result.append({
398
+ 'currency_code': stat['currency__code'],
399
+ 'currency_name': stat['currency__name'],
400
+ 'total_payments': stat['total_payments'],
401
+ 'total_amount': stat['total_amount'] or 0.0,
402
+ 'completed_payments': stat['completed_payments'],
403
+ 'average_amount': stat['avg_amount'] or 0.0,
404
+ 'success_rate': round(stat['success_rate'] or 0.0, 2),
405
+ })
406
+
407
+ return result
408
+
409
+ def get_provider_analytics(self) -> List[Dict[str, Any]]:
410
+ """
411
+ Get analytics by payment provider
412
+ """
413
+ provider_stats = UniversalPayment.objects.by_user(self.user).values('provider').annotate(
414
+ total_payments=Count('id'),
415
+ total_amount=Sum('amount_usd'),
416
+ completed_payments=Count(
417
+ 'id',
418
+ filter=Q(status=UniversalPayment.PaymentStatus.COMPLETED)
419
+ ),
420
+ success_rate=Count(
421
+ 'id',
422
+ filter=Q(status=UniversalPayment.PaymentStatus.COMPLETED)
423
+ ) * 100.0 / Count('id')
424
+ ).order_by('-total_payments')
425
+
426
+ result = []
427
+ for stat in provider_stats:
428
+ result.append({
429
+ 'provider': stat['provider'],
430
+ 'provider_display': dict(UniversalPayment.PaymentProvider.choices).get(
431
+ stat['provider'], stat['provider']
432
+ ),
433
+ 'total_payments': stat['total_payments'],
434
+ 'total_amount': stat['total_amount'] or 0.0,
435
+ 'completed_payments': stat['completed_payments'],
436
+ 'success_rate': round(stat['success_rate'] or 0.0, 2),
437
+ })
438
+
439
+ return result
@@ -0,0 +1,27 @@
1
+ """
2
+ 💰 Payments Overview Dashboard URLs
3
+
4
+ Nested router configuration for payments dashboard API endpoints.
5
+ """
6
+ from rest_framework.routers import DefaultRouter
7
+ from .views import PaymentsDashboardViewSet
8
+
9
+ # Create router
10
+ router = DefaultRouter()
11
+
12
+ # Register payments dashboard viewset
13
+ router.register(r'dashboard', PaymentsDashboardViewSet, basename='payments-dashboard')
14
+
15
+ # URL patterns
16
+ urlpatterns = router.urls
17
+
18
+ # Available endpoints:
19
+ # GET /api/payments/overview/dashboard/overview/ - Complete payments dashboard overview
20
+ # GET /api/payments/overview/dashboard/metrics/ - Payments dashboard metrics only
21
+ # GET /api/payments/overview/dashboard/chart_data/?period=30d - Payments chart data
22
+ # GET /api/payments/overview/dashboard/recent_payments/?limit=10 - Recent payments
23
+ # GET /api/payments/overview/dashboard/recent_transactions/?limit=10 - Recent transactions
24
+ # GET /api/payments/overview/dashboard/payment_analytics/?limit=10 - Payment analytics
25
+ # GET /api/payments/overview/dashboard/balance_overview/ - Balance overview only
26
+ # GET /api/payments/overview/dashboard/subscription_overview/ - Subscription overview only
27
+ # GET /api/payments/overview/dashboard/api_keys_overview/ - API keys overview only
@@ -0,0 +1,231 @@
1
+ """
2
+ 💰 Payments Overview Dashboard Views
3
+
4
+ API views for payments dashboard data using existing models.
5
+ """
6
+ from rest_framework import viewsets, status
7
+ from rest_framework.decorators import action
8
+ from rest_framework.response import Response
9
+ from rest_framework.permissions import IsAuthenticated
10
+ from drf_spectacular.utils import extend_schema, extend_schema_view
11
+ from drf_spectacular.openapi import OpenApiParameter
12
+
13
+ from .services import (
14
+ PaymentsDashboardMetricsService,
15
+ PaymentsUsageChartService,
16
+ RecentPaymentsService,
17
+ PaymentsAnalyticsService,
18
+ )
19
+ from .serializers import (
20
+ PaymentsDashboardOverviewSerializer,
21
+ PaymentsMetricsSerializer,
22
+ PaymentsChartResponseSerializer,
23
+ TimePeriodSerializer,
24
+ RecentPaymentSerializer,
25
+ RecentTransactionSerializer,
26
+ BalanceOverviewSerializer,
27
+ SubscriptionOverviewSerializer,
28
+ APIKeysOverviewSerializer,
29
+ )
30
+
31
+
32
+ @extend_schema_view(
33
+ overview=extend_schema(
34
+ summary="Payments Dashboard Overview",
35
+ description="Get complete payments dashboard overview with metrics, recent payments, and analytics",
36
+ responses={200: PaymentsDashboardOverviewSerializer}
37
+ ),
38
+ metrics=extend_schema(
39
+ summary="Payments Dashboard Metrics",
40
+ description="Get payments dashboard metrics including balance, subscriptions, API keys, and payments",
41
+ responses={200: PaymentsMetricsSerializer}
42
+ ),
43
+ chart_data=extend_schema(
44
+ summary="Payments Chart Data",
45
+ description="Get chart data for payments visualization",
46
+ parameters=[
47
+ OpenApiParameter(
48
+ name='period',
49
+ description='Time period for chart data',
50
+ required=False,
51
+ type=str,
52
+ enum=['7d', '30d', '90d', '1y'],
53
+ default='30d'
54
+ )
55
+ ],
56
+ responses={200: PaymentsChartResponseSerializer}
57
+ ),
58
+ recent_payments=extend_schema(
59
+ summary="Recent Payments",
60
+ description="Get recent payments for the user",
61
+ parameters=[
62
+ OpenApiParameter(
63
+ name='limit',
64
+ description='Number of payments to return',
65
+ required=False,
66
+ type=int,
67
+ default=10
68
+ )
69
+ ],
70
+ responses={200: RecentPaymentSerializer(many=True)}
71
+ ),
72
+ recent_transactions=extend_schema(
73
+ summary="Recent Transactions",
74
+ description="Get recent balance transactions for the user",
75
+ parameters=[
76
+ OpenApiParameter(
77
+ name='limit',
78
+ description='Number of transactions to return',
79
+ required=False,
80
+ type=int,
81
+ default=10
82
+ )
83
+ ],
84
+ responses={200: RecentTransactionSerializer(many=True)}
85
+ ),
86
+ payment_analytics=extend_schema(
87
+ summary="Payment Analytics",
88
+ description="Get analytics for payments by currency and provider",
89
+ parameters=[
90
+ OpenApiParameter(
91
+ name='limit',
92
+ description='Number of analytics items to return',
93
+ required=False,
94
+ type=int,
95
+ default=10
96
+ )
97
+ ],
98
+ responses={200: 'object'} # Generic object since it's a dict with currency_analytics and provider_analytics
99
+ ),
100
+ balance_overview=extend_schema(
101
+ summary="Balance Overview",
102
+ description="Get user balance overview",
103
+ responses={200: BalanceOverviewSerializer}
104
+ ),
105
+ subscription_overview=extend_schema(
106
+ summary="Subscription Overview",
107
+ description="Get current subscription overview",
108
+ responses={200: SubscriptionOverviewSerializer}
109
+ ),
110
+ api_keys_overview=extend_schema(
111
+ summary="API Keys Overview",
112
+ description="Get API keys overview",
113
+ responses={200: APIKeysOverviewSerializer}
114
+ )
115
+ )
116
+ class PaymentsDashboardViewSet(viewsets.GenericViewSet):
117
+ """
118
+ Payments dashboard data endpoints
119
+ """
120
+ permission_classes = [IsAuthenticated]
121
+
122
+ @action(detail=False, methods=['get'])
123
+ def overview(self, request):
124
+ """
125
+ Get complete payments dashboard overview
126
+ """
127
+ # Initialize services
128
+ metrics_service = PaymentsDashboardMetricsService(request.user)
129
+ chart_service = PaymentsUsageChartService(request.user)
130
+ payments_service = RecentPaymentsService(request.user)
131
+
132
+ # Get all data
133
+ data = {
134
+ 'metrics': metrics_service.get_dashboard_metrics(),
135
+ 'recent_payments': payments_service.get_recent_payments(limit=10),
136
+ 'recent_transactions': payments_service.get_recent_transactions(limit=10),
137
+ 'chart_data': chart_service.get_chart_data(period='30d')
138
+ }
139
+
140
+ return Response(data)
141
+
142
+ @action(detail=False, methods=['get'])
143
+ def metrics(self, request):
144
+ """
145
+ Get payments dashboard metrics only
146
+ """
147
+ service = PaymentsDashboardMetricsService(request.user)
148
+ metrics = service.get_dashboard_metrics()
149
+ return Response(metrics)
150
+
151
+ @action(detail=False, methods=['get'])
152
+ def chart_data(self, request):
153
+ """
154
+ Get payments usage chart data
155
+ """
156
+ period = request.query_params.get('period', '30d')
157
+
158
+ # Validate period
159
+ serializer = TimePeriodSerializer(data={'period': period})
160
+ if not serializer.is_valid():
161
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
162
+
163
+ service = PaymentsUsageChartService(request.user)
164
+ data = service.get_chart_data(period)
165
+ return Response(data)
166
+
167
+ @action(detail=False, methods=['get'])
168
+ def recent_payments(self, request):
169
+ """
170
+ Get recent payments
171
+ """
172
+ limit = int(request.query_params.get('limit', 10))
173
+ limit = min(limit, 50) # Max 50 payments
174
+
175
+ service = RecentPaymentsService(request.user)
176
+ payments = service.get_recent_payments(limit)
177
+ return Response(payments)
178
+
179
+ @action(detail=False, methods=['get'])
180
+ def recent_transactions(self, request):
181
+ """
182
+ Get recent balance transactions
183
+ """
184
+ limit = int(request.query_params.get('limit', 10))
185
+ limit = min(limit, 50) # Max 50 transactions
186
+
187
+ service = RecentPaymentsService(request.user)
188
+ transactions = service.get_recent_transactions(limit)
189
+ return Response(transactions)
190
+
191
+ @action(detail=False, methods=['get'])
192
+ def payment_analytics(self, request):
193
+ """
194
+ Get payment analytics
195
+ """
196
+ limit = int(request.query_params.get('limit', 10))
197
+ limit = min(limit, 20) # Max 20 analytics items
198
+
199
+ service = PaymentsAnalyticsService(request.user)
200
+ analytics = {
201
+ 'currency_analytics': service.get_payment_analytics(limit),
202
+ 'provider_analytics': service.get_provider_analytics(),
203
+ }
204
+ return Response(analytics)
205
+
206
+ @action(detail=False, methods=['get'])
207
+ def balance_overview(self, request):
208
+ """
209
+ Get balance overview only
210
+ """
211
+ service = PaymentsDashboardMetricsService(request.user)
212
+ balance = service.get_balance_overview()
213
+ return Response(balance)
214
+
215
+ @action(detail=False, methods=['get'])
216
+ def subscription_overview(self, request):
217
+ """
218
+ Get subscription overview only
219
+ """
220
+ service = PaymentsDashboardMetricsService(request.user)
221
+ subscription = service.get_subscription_overview()
222
+ return Response(subscription)
223
+
224
+ @action(detail=False, methods=['get'])
225
+ def api_keys_overview(self, request):
226
+ """
227
+ Get API keys overview only
228
+ """
229
+ service = PaymentsDashboardMetricsService(request.user)
230
+ api_keys = service.get_api_keys_overview()
231
+ return Response(api_keys)