django-cfg 1.3.3__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 +16 -6
- django_cfg/apps/payments/management/commands/currency_stats.py +72 -5
- 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/middleware/api_access.py +35 -34
- django_cfg/apps/payments/migrations/0001_initial.py +1 -1
- django_cfg/apps/payments/models/managers/api_key_managers.py +4 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
- django_cfg/apps/payments/models/subscriptions.py +0 -24
- django_cfg/apps/payments/services/cache/__init__.py +1 -1
- django_cfg/apps/payments/services/core/balance_service.py +13 -2
- 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/balance_signals.py +7 -4
- 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 +41 -1
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.5.dist-info}/RECORD +98 -65
- 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/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.3.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
"""
|
2
|
+
💰 Payments Overview Dashboard Serializers
|
3
|
+
|
4
|
+
DRF serializers for payments dashboard data with drf-spectacular integration.
|
5
|
+
"""
|
6
|
+
from rest_framework import serializers
|
7
|
+
from drf_spectacular.utils import extend_schema_field
|
8
|
+
from typing import Dict, Any
|
9
|
+
|
10
|
+
|
11
|
+
class BalanceOverviewSerializer(serializers.Serializer):
|
12
|
+
"""
|
13
|
+
User balance overview metrics
|
14
|
+
"""
|
15
|
+
current_balance = serializers.FloatField(help_text="Current balance in USD")
|
16
|
+
balance_display = serializers.CharField(help_text="Formatted balance display")
|
17
|
+
total_deposited = serializers.FloatField(help_text="Total amount deposited (lifetime)")
|
18
|
+
total_spent = serializers.FloatField(help_text="Total amount spent (lifetime)")
|
19
|
+
last_transaction_at = serializers.DateTimeField(allow_null=True, help_text="Last transaction timestamp")
|
20
|
+
has_transactions = serializers.BooleanField(help_text="Whether user has any transactions")
|
21
|
+
is_empty = serializers.BooleanField(help_text="Whether balance is zero")
|
22
|
+
|
23
|
+
|
24
|
+
class SubscriptionOverviewSerializer(serializers.Serializer):
|
25
|
+
"""
|
26
|
+
Current subscription overview
|
27
|
+
"""
|
28
|
+
tier = serializers.CharField(help_text="Subscription tier")
|
29
|
+
tier_display = serializers.CharField(help_text="Human-readable tier name")
|
30
|
+
status = serializers.CharField(help_text="Subscription status")
|
31
|
+
status_display = serializers.CharField(help_text="Human-readable status")
|
32
|
+
status_color = serializers.CharField(help_text="Color for status display")
|
33
|
+
is_active = serializers.BooleanField(help_text="Whether subscription is active")
|
34
|
+
is_expired = serializers.BooleanField(help_text="Whether subscription is expired")
|
35
|
+
days_remaining = serializers.IntegerField(help_text="Days until expiration")
|
36
|
+
|
37
|
+
# Limits and usage
|
38
|
+
requests_per_hour = serializers.IntegerField(help_text="Hourly request limit")
|
39
|
+
requests_per_day = serializers.IntegerField(help_text="Daily request limit")
|
40
|
+
total_requests = serializers.IntegerField(help_text="Total requests made")
|
41
|
+
usage_percentage = serializers.FloatField(help_text="Usage percentage for current period")
|
42
|
+
|
43
|
+
# Billing
|
44
|
+
monthly_cost_usd = serializers.FloatField(help_text="Monthly cost in USD")
|
45
|
+
cost_display = serializers.CharField(help_text="Formatted cost display")
|
46
|
+
|
47
|
+
# Dates
|
48
|
+
starts_at = serializers.DateTimeField(help_text="Subscription start date")
|
49
|
+
expires_at = serializers.DateTimeField(help_text="Subscription expiration date")
|
50
|
+
last_request_at = serializers.DateTimeField(allow_null=True, help_text="Last API request timestamp")
|
51
|
+
|
52
|
+
# Access
|
53
|
+
endpoint_groups_count = serializers.IntegerField(help_text="Number of accessible endpoint groups")
|
54
|
+
endpoint_groups = serializers.ListField(
|
55
|
+
child=serializers.CharField(),
|
56
|
+
help_text="List of accessible endpoint group names"
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
class APIKeysOverviewSerializer(serializers.Serializer):
|
61
|
+
"""
|
62
|
+
API keys overview metrics
|
63
|
+
"""
|
64
|
+
total_keys = serializers.IntegerField(help_text="Total number of API keys")
|
65
|
+
active_keys = serializers.IntegerField(help_text="Number of active API keys")
|
66
|
+
expired_keys = serializers.IntegerField(help_text="Number of expired API keys")
|
67
|
+
total_requests = serializers.IntegerField(help_text="Total requests across all keys")
|
68
|
+
last_used_at = serializers.DateTimeField(allow_null=True, help_text="When any key was last used")
|
69
|
+
|
70
|
+
# Recent keys info
|
71
|
+
most_used_key_name = serializers.CharField(allow_null=True, help_text="Name of most used API key")
|
72
|
+
most_used_key_requests = serializers.IntegerField(help_text="Requests count for most used key")
|
73
|
+
|
74
|
+
# Expiring keys warning
|
75
|
+
expiring_soon_count = serializers.IntegerField(help_text="Number of keys expiring within 7 days")
|
76
|
+
|
77
|
+
|
78
|
+
class PaymentOverviewSerializer(serializers.Serializer):
|
79
|
+
"""
|
80
|
+
Payments overview metrics
|
81
|
+
"""
|
82
|
+
total_payments = serializers.IntegerField(help_text="Total number of payments")
|
83
|
+
completed_payments = serializers.IntegerField(help_text="Number of completed payments")
|
84
|
+
pending_payments = serializers.IntegerField(help_text="Number of pending payments")
|
85
|
+
failed_payments = serializers.IntegerField(help_text="Number of failed payments")
|
86
|
+
|
87
|
+
# Amounts
|
88
|
+
total_amount_usd = serializers.FloatField(help_text="Total payment amount in USD")
|
89
|
+
completed_amount_usd = serializers.FloatField(help_text="Total completed amount in USD")
|
90
|
+
average_payment_usd = serializers.FloatField(help_text="Average payment amount in USD")
|
91
|
+
|
92
|
+
# Success rate
|
93
|
+
success_rate = serializers.FloatField(help_text="Payment success rate percentage")
|
94
|
+
|
95
|
+
# Recent activity
|
96
|
+
last_payment_at = serializers.DateTimeField(allow_null=True, help_text="Last payment timestamp")
|
97
|
+
payments_this_month = serializers.IntegerField(help_text="Number of payments this month")
|
98
|
+
amount_this_month = serializers.FloatField(help_text="Total amount this month")
|
99
|
+
|
100
|
+
# Popular currencies
|
101
|
+
top_currency = serializers.CharField(allow_null=True, help_text="Most used currency")
|
102
|
+
top_currency_count = serializers.IntegerField(help_text="Usage count for top currency")
|
103
|
+
|
104
|
+
|
105
|
+
class PaymentsMetricsSerializer(serializers.Serializer):
|
106
|
+
"""
|
107
|
+
Complete payments dashboard metrics
|
108
|
+
"""
|
109
|
+
balance = BalanceOverviewSerializer(help_text="Balance overview")
|
110
|
+
subscription = SubscriptionOverviewSerializer(allow_null=True, help_text="Subscription overview")
|
111
|
+
api_keys = APIKeysOverviewSerializer(help_text="API keys overview")
|
112
|
+
payments = PaymentOverviewSerializer(help_text="Payments overview")
|
113
|
+
|
114
|
+
|
115
|
+
class RecentPaymentSerializer(serializers.Serializer):
|
116
|
+
"""
|
117
|
+
Recent payment item
|
118
|
+
"""
|
119
|
+
id = serializers.UUIDField(help_text="Payment ID")
|
120
|
+
internal_payment_id = serializers.CharField(help_text="Internal payment ID")
|
121
|
+
amount_usd = serializers.FloatField(help_text="Payment amount in USD")
|
122
|
+
amount_display = serializers.CharField(help_text="Formatted amount display")
|
123
|
+
currency_code = serializers.CharField(help_text="Currency code")
|
124
|
+
status = serializers.CharField(help_text="Payment status")
|
125
|
+
status_display = serializers.CharField(help_text="Human-readable status")
|
126
|
+
status_color = serializers.CharField(help_text="Color for status display")
|
127
|
+
provider = serializers.CharField(help_text="Payment provider")
|
128
|
+
created_at = serializers.DateTimeField(help_text="Payment creation timestamp")
|
129
|
+
completed_at = serializers.DateTimeField(allow_null=True, help_text="Payment completion timestamp")
|
130
|
+
|
131
|
+
# Status flags
|
132
|
+
is_pending = serializers.BooleanField(help_text="Whether payment is pending")
|
133
|
+
is_completed = serializers.BooleanField(help_text="Whether payment is completed")
|
134
|
+
is_failed = serializers.BooleanField(help_text="Whether payment failed")
|
135
|
+
|
136
|
+
|
137
|
+
class RecentTransactionSerializer(serializers.Serializer):
|
138
|
+
"""
|
139
|
+
Recent transaction item
|
140
|
+
"""
|
141
|
+
id = serializers.UUIDField(help_text="Transaction ID")
|
142
|
+
transaction_type = serializers.CharField(help_text="Transaction type")
|
143
|
+
amount_usd = serializers.FloatField(help_text="Transaction amount in USD")
|
144
|
+
amount_display = serializers.CharField(help_text="Formatted amount display")
|
145
|
+
balance_after = serializers.FloatField(help_text="Balance after transaction")
|
146
|
+
description = serializers.CharField(help_text="Transaction description")
|
147
|
+
created_at = serializers.DateTimeField(help_text="Transaction timestamp")
|
148
|
+
payment_id = serializers.CharField(allow_null=True, help_text="Related payment ID")
|
149
|
+
|
150
|
+
# Type info
|
151
|
+
is_credit = serializers.BooleanField(help_text="Whether this is a credit transaction")
|
152
|
+
is_debit = serializers.BooleanField(help_text="Whether this is a debit transaction")
|
153
|
+
type_color = serializers.CharField(help_text="Color for transaction type display")
|
154
|
+
|
155
|
+
|
156
|
+
class ChartDataPointSerializer(serializers.Serializer):
|
157
|
+
"""
|
158
|
+
Chart data point for payments analytics
|
159
|
+
"""
|
160
|
+
x = serializers.CharField(help_text="X-axis value (date)")
|
161
|
+
y = serializers.FloatField(help_text="Y-axis value (amount or count)")
|
162
|
+
|
163
|
+
|
164
|
+
class ChartSeriesSerializer(serializers.Serializer):
|
165
|
+
"""
|
166
|
+
Chart series data for payments visualization
|
167
|
+
"""
|
168
|
+
name = serializers.CharField(help_text="Series name")
|
169
|
+
data = ChartDataPointSerializer(many=True, help_text="Data points")
|
170
|
+
color = serializers.CharField(help_text="Series color")
|
171
|
+
|
172
|
+
|
173
|
+
class PaymentsChartResponseSerializer(serializers.Serializer):
|
174
|
+
"""
|
175
|
+
Complete chart response for payments analytics
|
176
|
+
"""
|
177
|
+
series = ChartSeriesSerializer(many=True, help_text="Chart series data")
|
178
|
+
period = serializers.CharField(help_text="Time period")
|
179
|
+
total_amount = serializers.FloatField(help_text="Total amount for period")
|
180
|
+
total_payments = serializers.IntegerField(help_text="Total payments for period")
|
181
|
+
success_rate = serializers.FloatField(help_text="Success rate for period")
|
182
|
+
|
183
|
+
|
184
|
+
class PaymentsDashboardOverviewSerializer(serializers.Serializer):
|
185
|
+
"""
|
186
|
+
Complete payments dashboard overview response
|
187
|
+
"""
|
188
|
+
metrics = PaymentsMetricsSerializer(help_text="Dashboard metrics")
|
189
|
+
recent_payments = RecentPaymentSerializer(many=True, help_text="Recent payments")
|
190
|
+
recent_transactions = RecentTransactionSerializer(many=True, help_text="Recent transactions")
|
191
|
+
chart_data = PaymentsChartResponseSerializer(help_text="Chart data for analytics")
|
192
|
+
|
193
|
+
|
194
|
+
class TimePeriodSerializer(serializers.Serializer):
|
195
|
+
"""
|
196
|
+
Time period filter for charts and analytics
|
197
|
+
"""
|
198
|
+
PERIOD_CHOICES = [
|
199
|
+
('7d', 'Last 7 days'),
|
200
|
+
('30d', 'Last 30 days'),
|
201
|
+
('90d', 'Last 90 days'),
|
202
|
+
('1y', 'Last year'),
|
203
|
+
]
|
204
|
+
|
205
|
+
period = serializers.ChoiceField(choices=PERIOD_CHOICES, default='30d')
|
@@ -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
|