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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/payments/admin_interface/old/payments/base.html +175 -0
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +125 -0
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +113 -0
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +35 -0
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +309 -0
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +518 -0
- django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/components.css +248 -9
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +163 -0
- django_cfg/apps/payments/admin_interface/serializers/__init__.py +39 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +149 -0
- django_cfg/apps/payments/admin_interface/serializers/webhook_serializers.py +114 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +55 -90
- django_cfg/apps/payments/admin_interface/templates/payments/components/dialog.html +81 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_help_dialog.html +112 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_status.html +175 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +21 -17
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +123 -250
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +170 -269
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +152 -355
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +202 -551
- django_cfg/apps/payments/admin_interface/views/__init__.py +25 -14
- django_cfg/apps/payments/admin_interface/views/api/__init__.py +20 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +191 -0
- django_cfg/apps/payments/admin_interface/views/api/stats.py +206 -0
- django_cfg/apps/payments/admin_interface/views/api/users.py +60 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +257 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +70 -0
- django_cfg/apps/payments/admin_interface/views/base.py +114 -0
- django_cfg/apps/payments/admin_interface/views/dashboard.py +60 -0
- django_cfg/apps/payments/admin_interface/views/forms.py +94 -0
- django_cfg/apps/payments/config/helpers.py +2 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +429 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +443 -0
- django_cfg/apps/payments/management/commands/manage_currencies.py +9 -20
- django_cfg/apps/payments/management/commands/manage_providers.py +5 -5
- django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
- django_cfg/apps/payments/management/commands/test_providers.py +434 -0
- django_cfg/apps/payments/middleware/api_access.py +35 -34
- django_cfg/apps/payments/migrations/0001_initial.py +1 -1
- django_cfg/apps/payments/models/balance.py +5 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +6 -2
- django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
- django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
- django_cfg/apps/payments/models/subscriptions.py +0 -24
- django_cfg/apps/payments/services/cache/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
- django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
- django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
- django_cfg/apps/payments/services/cache_service/keys.py +49 -0
- django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
- django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
- django_cfg/apps/payments/services/core/balance_service.py +13 -2
- django_cfg/apps/payments/services/core/payment_service.py +49 -22
- django_cfg/apps/payments/services/integrations/ngrok_service.py +3 -3
- django_cfg/apps/payments/services/providers/registry.py +20 -0
- django_cfg/apps/payments/signals/api_key_signals.py +2 -2
- django_cfg/apps/payments/signals/balance_signals.py +8 -5
- django_cfg/apps/payments/static/payments/js/api-client.js +385 -0
- django_cfg/apps/payments/static/payments/js/ngrok-status.js +58 -0
- django_cfg/apps/payments/static/payments/js/payment-dashboard.js +50 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +175 -0
- django_cfg/apps/payments/static/payments/js/payment-list.js +95 -0
- django_cfg/apps/payments/static/payments/js/webhook-dashboard.js +154 -0
- django_cfg/apps/payments/urls.py +4 -0
- django_cfg/apps/payments/urls_admin.py +37 -18
- django_cfg/apps/payments/views/api/api_keys.py +14 -0
- django_cfg/apps/payments/views/api/base.py +1 -0
- django_cfg/apps/payments/views/api/currencies.py +2 -2
- django_cfg/apps/payments/views/api/payments.py +11 -5
- django_cfg/apps/payments/views/api/subscriptions.py +36 -31
- django_cfg/apps/payments/views/overview/__init__.py +40 -0
- django_cfg/apps/payments/views/overview/serializers.py +205 -0
- django_cfg/apps/payments/views/overview/services.py +439 -0
- django_cfg/apps/payments/views/overview/urls.py +27 -0
- django_cfg/apps/payments/views/overview/views.py +231 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +20 -6
- django_cfg/apps/payments/views/serializers/balances.py +5 -8
- django_cfg/apps/payments/views/serializers/currencies.py +2 -6
- django_cfg/apps/payments/views/serializers/payments.py +37 -32
- django_cfg/apps/payments/views/serializers/subscriptions.py +4 -26
- django_cfg/apps/urls.py +2 -1
- django_cfg/core/config.py +25 -15
- django_cfg/core/generation.py +12 -12
- django_cfg/core/integration/display/startup.py +1 -1
- django_cfg/core/validation.py +4 -4
- django_cfg/management/commands/show_config.py +2 -2
- django_cfg/management/commands/tree.py +1 -3
- django_cfg/middleware/__init__.py +2 -0
- django_cfg/middleware/static_nocache.py +55 -0
- django_cfg/models/payments.py +13 -15
- django_cfg/models/security.py +15 -0
- django_cfg/modules/django_ngrok.py +6 -0
- django_cfg/modules/django_unfold/dashboard.py +1 -3
- django_cfg/utils/smart_defaults.py +51 -5
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/RECORD +111 -69
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +0 -38
- django_cfg/apps/payments/admin_interface/views/payment_views.py +0 -259
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +0 -37
- django_cfg/apps/payments/services/cache/cache_service.py +0 -235
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/loading_spinner.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/notification.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/provider_card.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/currency_converter.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/payment_status.html +0 -0
- /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/dashboard.css +0 -0
- /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/components.js +0 -0
- /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/utils.js +0 -0
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
- {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)
|