django-cfg 1.3.3__py3-none-any.whl → 1.3.7__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/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.7.dist-info}/METADATA +1 -1
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.7.dist-info}/RECORD +97 -64
- 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.7.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.7.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.3.dist-info → django_cfg-1.3.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
/**
|
2
|
+
* Payment List Component
|
3
|
+
* Handles payment listing, filtering, and pagination
|
4
|
+
*/
|
5
|
+
function paymentList() {
|
6
|
+
return {
|
7
|
+
loading: false,
|
8
|
+
payments: [],
|
9
|
+
filters: {
|
10
|
+
search: '',
|
11
|
+
status: ''
|
12
|
+
},
|
13
|
+
currentPage: 1,
|
14
|
+
pageSize: 10,
|
15
|
+
|
16
|
+
get filteredPayments() {
|
17
|
+
let filtered = this.payments;
|
18
|
+
|
19
|
+
if (this.filters.search) {
|
20
|
+
const search = this.filters.search.toLowerCase();
|
21
|
+
filtered = filtered.filter(payment =>
|
22
|
+
payment.id.toLowerCase().includes(search) ||
|
23
|
+
payment.external_id?.toLowerCase().includes(search) ||
|
24
|
+
payment.provider.toLowerCase().includes(search)
|
25
|
+
);
|
26
|
+
}
|
27
|
+
|
28
|
+
if (this.filters.status) {
|
29
|
+
filtered = filtered.filter(payment => payment.status === this.filters.status);
|
30
|
+
}
|
31
|
+
|
32
|
+
return filtered;
|
33
|
+
},
|
34
|
+
|
35
|
+
get paginatedPayments() {
|
36
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
37
|
+
const end = start + this.pageSize;
|
38
|
+
return this.filteredPayments.slice(start, end);
|
39
|
+
},
|
40
|
+
|
41
|
+
async init() {
|
42
|
+
await this.loadPayments();
|
43
|
+
// Auto-refresh every 30 seconds
|
44
|
+
setInterval(() => this.loadPayments(), 30000);
|
45
|
+
},
|
46
|
+
|
47
|
+
async loadPayments() {
|
48
|
+
this.loading = true;
|
49
|
+
try {
|
50
|
+
const response = await PaymentAPI.admin.payments.list(this.filters);
|
51
|
+
this.payments = response.payments || response.results || [];
|
52
|
+
this.pagination = {
|
53
|
+
total: response.total || response.count || 0,
|
54
|
+
page: response.page || 1,
|
55
|
+
per_page: response.per_page || 50,
|
56
|
+
has_next: response.has_next || false,
|
57
|
+
has_previous: response.has_previous || false
|
58
|
+
};
|
59
|
+
} catch (error) {
|
60
|
+
console.error('Failed to load payments:', error);
|
61
|
+
this.payments = [];
|
62
|
+
PaymentAPI.utils.showNotification('Failed to load payments', 'error');
|
63
|
+
} finally {
|
64
|
+
this.loading = false;
|
65
|
+
}
|
66
|
+
},
|
67
|
+
|
68
|
+
async refreshPayments() {
|
69
|
+
await this.loadPayments();
|
70
|
+
PaymentAPI.utils.showNotification('Payments refreshed', 'success');
|
71
|
+
},
|
72
|
+
|
73
|
+
async refreshPayment(paymentId) {
|
74
|
+
try {
|
75
|
+
const updatedPayment = await PaymentAPI.payments.get(paymentId);
|
76
|
+
const index = this.payments.findIndex(p => p.id === paymentId);
|
77
|
+
if (index !== -1) {
|
78
|
+
this.payments[index] = updatedPayment;
|
79
|
+
}
|
80
|
+
PaymentAPI.utils.showNotification('Payment updated', 'success');
|
81
|
+
} catch (error) {
|
82
|
+
console.error('Failed to refresh payment:', error);
|
83
|
+
PaymentAPI.utils.showNotification('Failed to refresh payment', 'error');
|
84
|
+
}
|
85
|
+
},
|
86
|
+
|
87
|
+
applyFilters() {
|
88
|
+
this.currentPage = 1;
|
89
|
+
},
|
90
|
+
|
91
|
+
formatDate(dateString) {
|
92
|
+
return PaymentAPI.utils.formatDate(dateString);
|
93
|
+
}
|
94
|
+
};
|
95
|
+
}
|
@@ -0,0 +1,154 @@
|
|
1
|
+
/**
|
2
|
+
* Webhook Dashboard Component
|
3
|
+
* Handles webhook events monitoring and testing
|
4
|
+
*/
|
5
|
+
function webhookDashboard() {
|
6
|
+
return {
|
7
|
+
loading: false,
|
8
|
+
testLoading: false,
|
9
|
+
events: [],
|
10
|
+
filters: {
|
11
|
+
event_type: '',
|
12
|
+
status: ''
|
13
|
+
},
|
14
|
+
stats: {
|
15
|
+
total: 0,
|
16
|
+
successful: 0,
|
17
|
+
failed: 0,
|
18
|
+
successRate: 0
|
19
|
+
},
|
20
|
+
testForm: {
|
21
|
+
url: '',
|
22
|
+
event_type: ''
|
23
|
+
},
|
24
|
+
showEventModal: false,
|
25
|
+
selectedEvent: null,
|
26
|
+
|
27
|
+
get filteredEvents() {
|
28
|
+
let filtered = this.events;
|
29
|
+
|
30
|
+
if (this.filters.event_type) {
|
31
|
+
filtered = filtered.filter(event => event.event_type === this.filters.event_type);
|
32
|
+
}
|
33
|
+
|
34
|
+
if (this.filters.status) {
|
35
|
+
filtered = filtered.filter(event => event.status === this.filters.status);
|
36
|
+
}
|
37
|
+
|
38
|
+
return filtered;
|
39
|
+
},
|
40
|
+
|
41
|
+
async init() {
|
42
|
+
await this.loadEvents();
|
43
|
+
await this.loadStats();
|
44
|
+
// Auto-refresh every 30 seconds
|
45
|
+
setInterval(() => this.refreshEvents(), 30000);
|
46
|
+
},
|
47
|
+
|
48
|
+
async loadEvents() {
|
49
|
+
this.loading = true;
|
50
|
+
try {
|
51
|
+
const response = await PaymentAPI.admin.webhooks.events.list();
|
52
|
+
this.events = response.events || [];
|
53
|
+
this.pagination = {
|
54
|
+
total: response.total || 0,
|
55
|
+
page: response.page || 1,
|
56
|
+
per_page: response.per_page || 50,
|
57
|
+
has_next: response.has_next || false,
|
58
|
+
has_previous: response.has_previous || false
|
59
|
+
};
|
60
|
+
} catch (error) {
|
61
|
+
console.error('Failed to load events:', error);
|
62
|
+
this.events = [];
|
63
|
+
PaymentAPI.utils.showNotification('Failed to load webhook events', 'error');
|
64
|
+
} finally {
|
65
|
+
this.loading = false;
|
66
|
+
}
|
67
|
+
},
|
68
|
+
|
69
|
+
async loadStats() {
|
70
|
+
try {
|
71
|
+
this.stats = await PaymentAPI.admin.webhooks.stats();
|
72
|
+
} catch (error) {
|
73
|
+
console.error('Failed to load stats:', error);
|
74
|
+
// Set default stats on error
|
75
|
+
this.stats = {
|
76
|
+
total: 0,
|
77
|
+
successful: 0,
|
78
|
+
failed: 0,
|
79
|
+
successRate: 0
|
80
|
+
};
|
81
|
+
PaymentAPI.utils.showNotification('Failed to load webhook stats', 'error');
|
82
|
+
}
|
83
|
+
},
|
84
|
+
|
85
|
+
async refreshEvents() {
|
86
|
+
await this.loadEvents();
|
87
|
+
await this.loadStats();
|
88
|
+
},
|
89
|
+
|
90
|
+
async clearEvents() {
|
91
|
+
if (confirm('Are you sure you want to clear all events?')) {
|
92
|
+
try {
|
93
|
+
await PaymentAPI.admin.webhooks.events.clearAll(1);
|
94
|
+
this.events = [];
|
95
|
+
await this.loadStats();
|
96
|
+
PaymentAPI.utils.showNotification('All events cleared', 'success');
|
97
|
+
} catch (error) {
|
98
|
+
console.error('Failed to clear events:', error);
|
99
|
+
PaymentAPI.utils.showNotification('Failed to clear events', 'error');
|
100
|
+
}
|
101
|
+
}
|
102
|
+
},
|
103
|
+
|
104
|
+
async testWebhook() {
|
105
|
+
this.testLoading = true;
|
106
|
+
try {
|
107
|
+
await PaymentAPI.admin.webhookTest.send(this.testForm.url, this.testForm.event_type);
|
108
|
+
PaymentAPI.utils.showNotification('Test webhook sent successfully!', 'success');
|
109
|
+
this.testForm = { url: '', event_type: '' };
|
110
|
+
await this.refreshEvents();
|
111
|
+
} catch (error) {
|
112
|
+
console.error('Failed to test webhook:', error);
|
113
|
+
PaymentAPI.utils.showNotification('Failed to send test webhook', 'error');
|
114
|
+
} finally {
|
115
|
+
this.testLoading = false;
|
116
|
+
}
|
117
|
+
},
|
118
|
+
|
119
|
+
async retryEvent(eventId) {
|
120
|
+
try {
|
121
|
+
await PaymentAPI.admin.webhooks.events.retry(1, eventId);
|
122
|
+
await this.refreshEvents();
|
123
|
+
PaymentAPI.utils.showNotification('Event retried', 'success');
|
124
|
+
} catch (error) {
|
125
|
+
console.error('Failed to retry event:', error);
|
126
|
+
PaymentAPI.utils.showNotification('Failed to retry event', 'error');
|
127
|
+
}
|
128
|
+
},
|
129
|
+
|
130
|
+
async retryFailedEvents() {
|
131
|
+
try {
|
132
|
+
await PaymentAPI.admin.webhooks.events.retryFailed(1);
|
133
|
+
await this.refreshEvents();
|
134
|
+
PaymentAPI.utils.showNotification('Failed events retried', 'success');
|
135
|
+
} catch (error) {
|
136
|
+
console.error('Failed to retry failed events:', error);
|
137
|
+
PaymentAPI.utils.showNotification('Failed to retry events', 'error');
|
138
|
+
}
|
139
|
+
},
|
140
|
+
|
141
|
+
viewEvent(event) {
|
142
|
+
this.selectedEvent = event;
|
143
|
+
this.showEventModal = true;
|
144
|
+
},
|
145
|
+
|
146
|
+
applyFilters() {
|
147
|
+
// Filters are applied automatically via computed property
|
148
|
+
},
|
149
|
+
|
150
|
+
formatDate(dateString) {
|
151
|
+
return PaymentAPI.utils.formatDate(dateString);
|
152
|
+
}
|
153
|
+
};
|
154
|
+
}
|
django_cfg/apps/payments/urls.py
CHANGED
@@ -16,6 +16,7 @@ from .views.api import (
|
|
16
16
|
APIKeyViewSet, UserAPIKeyViewSet, APIKeyCreateView, APIKeyValidateView,
|
17
17
|
UniversalWebhookView, webhook_health_check, webhook_stats, supported_providers,
|
18
18
|
)
|
19
|
+
from .views.overview import urls as overview_urls
|
19
20
|
|
20
21
|
app_name = 'cfg_payments'
|
21
22
|
|
@@ -74,4 +75,7 @@ urlpatterns = [
|
|
74
75
|
|
75
76
|
# Health check endpoint
|
76
77
|
path('health/', PaymentViewSet.as_view({'get': 'health'}), name='health-check'),
|
78
|
+
|
79
|
+
# Overview dashboard endpoints
|
80
|
+
path('overview/', include(overview_urls)),
|
77
81
|
]
|
@@ -1,49 +1,68 @@
|
|
1
1
|
"""
|
2
2
|
Admin URLs for Universal Payment System v2.0.
|
3
3
|
|
4
|
-
Internal dashboard and management interfaces.
|
4
|
+
Internal dashboard and management interfaces with DRF nested routing.
|
5
5
|
All URLs require staff/superuser access.
|
6
6
|
"""
|
7
7
|
|
8
8
|
from django.urls import path, include
|
9
9
|
from django.contrib.admin.views.decorators import staff_member_required
|
10
|
+
from rest_framework.routers import DefaultRouter
|
11
|
+
from rest_framework_nested import routers
|
10
12
|
|
11
13
|
from .admin_interface.views import (
|
12
|
-
|
14
|
+
PaymentDashboardView,
|
13
15
|
PaymentFormView,
|
14
|
-
|
16
|
+
PaymentDetailView,
|
15
17
|
PaymentListView,
|
16
|
-
|
17
|
-
|
18
|
+
AdminPaymentViewSet,
|
19
|
+
AdminWebhookViewSet,
|
20
|
+
AdminWebhookEventViewSet,
|
21
|
+
WebhookTestViewSet,
|
22
|
+
AdminStatsViewSet,
|
23
|
+
AdminUserViewSet,
|
18
24
|
)
|
25
|
+
from .admin_interface.views.dashboard import WebhookDashboardView
|
19
26
|
|
20
27
|
app_name = 'cfg_payments_admin'
|
21
28
|
|
29
|
+
# DRF Routers for Admin API
|
30
|
+
admin_router = DefaultRouter()
|
31
|
+
admin_router.register(r'payments', AdminPaymentViewSet, basename='admin-payment')
|
32
|
+
admin_router.register(r'webhooks', AdminWebhookViewSet, basename='admin-webhook')
|
33
|
+
admin_router.register(r'stats', AdminStatsViewSet, basename='admin-stats')
|
34
|
+
admin_router.register(r'users', AdminUserViewSet, basename='admin-user')
|
35
|
+
|
36
|
+
# Nested router for webhook events
|
37
|
+
webhook_events_router = routers.NestedSimpleRouter(admin_router, r'webhooks', lookup='webhook')
|
38
|
+
webhook_events_router.register(r'events', AdminWebhookEventViewSet, basename='admin-webhook-events')
|
39
|
+
|
40
|
+
# Public API router (no authentication required)
|
41
|
+
public_router = DefaultRouter()
|
42
|
+
public_router.register(r'webhooks', WebhookTestViewSet, basename='webhook-test')
|
43
|
+
|
22
44
|
urlpatterns = [
|
23
|
-
#
|
45
|
+
# Template Views
|
24
46
|
path('', staff_member_required(PaymentDashboardView.as_view()), name='dashboard'),
|
25
47
|
path('dashboard/', staff_member_required(PaymentDashboardView.as_view()), name='dashboard_alt'),
|
26
48
|
|
27
|
-
# Payment management
|
49
|
+
# Payment management templates
|
28
50
|
path('payments/', include([
|
29
51
|
path('', staff_member_required(PaymentListView.as_view()), name='payment-list'),
|
30
|
-
path('create/', staff_member_required(PaymentFormView.as_view()), name='payment-
|
31
|
-
path('<uuid:pk>/', staff_member_required(
|
32
|
-
path('status/<uuid:pk>/', staff_member_required(PaymentStatusView.as_view()), name='payment-status'),
|
52
|
+
path('create/', staff_member_required(PaymentFormView.as_view()), name='payment-form'),
|
53
|
+
path('<uuid:pk>/', staff_member_required(PaymentDetailView.as_view()), name='payment-detail'),
|
33
54
|
])),
|
34
55
|
|
35
|
-
# Webhook management
|
56
|
+
# Webhook management templates
|
36
57
|
path('webhooks/', include([
|
37
58
|
path('', staff_member_required(WebhookDashboardView.as_view()), name='webhook-dashboard'),
|
38
59
|
path('dashboard/', staff_member_required(WebhookDashboardView.as_view()), name='webhook-dashboard-alt'),
|
39
60
|
])),
|
40
61
|
|
41
|
-
#
|
42
|
-
path('
|
43
|
-
path('
|
62
|
+
# API Routes with DRF ViewSets
|
63
|
+
path('api/', include([
|
64
|
+
path('', include(admin_router.urls)),
|
65
|
+
path('', include(webhook_events_router.urls)),
|
66
|
+
path('', include(public_router.urls)), # Public API endpoints
|
44
67
|
])),
|
45
|
-
|
46
|
-
# Development/testing tools (only in DEBUG mode)
|
47
|
-
# path('test/', PaymentTestView.as_view(), name='test'),
|
48
|
-
# path('debug/', PaymentDebugView.as_view(), name='debug'),
|
49
68
|
]
|
@@ -11,6 +11,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
|
11
11
|
from django.contrib.auth import get_user_model
|
12
12
|
from django.db import models
|
13
13
|
from django.utils import timezone
|
14
|
+
from drf_spectacular.utils import extend_schema, extend_schema_view
|
14
15
|
|
15
16
|
from .base import PaymentBaseViewSet, NestedPaymentViewSet, ReadOnlyPaymentViewSet
|
16
17
|
from ...models import APIKey
|
@@ -21,6 +22,7 @@ from ..serializers.api_keys import (
|
|
21
22
|
APIKeyUpdateSerializer,
|
22
23
|
APIKeyActionSerializer,
|
23
24
|
APIKeyValidationSerializer,
|
25
|
+
APIKeyValidationResponseSerializer,
|
24
26
|
APIKeyStatsSerializer,
|
25
27
|
)
|
26
28
|
from django_cfg.modules.django_logger import get_logger
|
@@ -78,6 +80,12 @@ class APIKeyViewSet(PaymentBaseViewSet):
|
|
78
80
|
|
79
81
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
80
82
|
|
83
|
+
@extend_schema(
|
84
|
+
summary="Validate API Key",
|
85
|
+
description="Validate an API key and return key information",
|
86
|
+
request=APIKeyValidationSerializer,
|
87
|
+
responses={200: APIKeyValidationResponseSerializer}
|
88
|
+
)
|
81
89
|
@action(detail=False, methods=['post'])
|
82
90
|
def validate_key(self, request):
|
83
91
|
"""
|
@@ -376,6 +384,12 @@ class APIKeyValidateView(generics.GenericAPIView):
|
|
376
384
|
serializer_class = APIKeyValidationSerializer
|
377
385
|
permission_classes = [permissions.IsAuthenticated]
|
378
386
|
|
387
|
+
@extend_schema(
|
388
|
+
summary="Validate API Key (Standalone)",
|
389
|
+
description="Standalone endpoint to validate an API key and return key information",
|
390
|
+
request=APIKeyValidationSerializer,
|
391
|
+
responses={200: APIKeyValidationResponseSerializer}
|
392
|
+
)
|
379
393
|
def post(self, request, *args, **kwargs):
|
380
394
|
"""Validate API key."""
|
381
395
|
serializer = self.get_serializer(data=request.data)
|
@@ -32,6 +32,7 @@ class PaymentBaseViewSet(viewsets.ModelViewSet):
|
|
32
32
|
permission_classes = [permissions.IsAuthenticated]
|
33
33
|
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
34
34
|
ordering = ['-created_at']
|
35
|
+
versioning_class = None # Disable versioning for payments API
|
35
36
|
|
36
37
|
# Serializer classes mapping for different actions
|
37
38
|
serializer_classes = {}
|
@@ -195,7 +195,7 @@ class NetworkViewSet(ReadOnlyPaymentViewSet):
|
|
195
195
|
queryset = Network.objects.filter(is_active=True)
|
196
196
|
serializer_class = NetworkSerializer
|
197
197
|
permission_classes = [permissions.IsAuthenticated]
|
198
|
-
filterset_fields = ['
|
198
|
+
filterset_fields = ['native_currency__code', 'is_active']
|
199
199
|
search_fields = ['name', 'code']
|
200
200
|
ordering_fields = ['name', 'code', 'created_at']
|
201
201
|
|
@@ -251,7 +251,7 @@ class ProviderCurrencyViewSet(ReadOnlyPaymentViewSet):
|
|
251
251
|
queryset = ProviderCurrency.objects.filter(is_enabled=True)
|
252
252
|
serializer_class = ProviderCurrencySerializer
|
253
253
|
permission_classes = [permissions.IsAuthenticated]
|
254
|
-
filterset_fields = ['provider', '
|
254
|
+
filterset_fields = ['provider', 'currency__code', 'network__code', 'is_enabled']
|
255
255
|
search_fields = ['provider_currency_code']
|
256
256
|
ordering_fields = ['provider', 'created_at', 'min_amount', 'max_amount']
|
257
257
|
|
@@ -37,8 +37,8 @@ class PaymentViewSet(PaymentBaseViewSet):
|
|
37
37
|
|
38
38
|
queryset = UniversalPayment.objects.all()
|
39
39
|
serializer_class = PaymentSerializer
|
40
|
-
permission_classes = [permissions.
|
41
|
-
filterset_fields = ['status', 'provider', '
|
40
|
+
permission_classes = [permissions.IsAuthenticated] # Allow authenticated users
|
41
|
+
filterset_fields = ['status', 'provider', 'currency__code', 'user']
|
42
42
|
search_fields = ['description', 'provider_payment_id', 'transaction_hash']
|
43
43
|
ordering_fields = ['created_at', 'updated_at', 'amount_usd', 'expires_at']
|
44
44
|
|
@@ -52,9 +52,15 @@ class PaymentViewSet(PaymentBaseViewSet):
|
|
52
52
|
|
53
53
|
def get_queryset(self):
|
54
54
|
"""Optimized queryset with related objects."""
|
55
|
-
|
56
|
-
'
|
55
|
+
queryset = super().get_queryset().select_related('user').prefetch_related(
|
56
|
+
'user__payment_balance'
|
57
57
|
)
|
58
|
+
|
59
|
+
# Non-staff users can only see their own payments
|
60
|
+
if not self.request.user.is_staff:
|
61
|
+
queryset = queryset.filter(user=self.request.user)
|
62
|
+
|
63
|
+
return queryset
|
58
64
|
|
59
65
|
@action(detail=True, methods=['post'])
|
60
66
|
def check_status(self, request, pk=None):
|
@@ -178,7 +184,7 @@ class UserPaymentViewSet(NestedPaymentViewSet):
|
|
178
184
|
queryset = UniversalPayment.objects.all()
|
179
185
|
serializer_class = PaymentSerializer
|
180
186
|
permission_classes = [permissions.IsAuthenticated]
|
181
|
-
filterset_fields = ['status', 'provider', '
|
187
|
+
filterset_fields = ['status', 'provider', 'currency__code']
|
182
188
|
search_fields = ['description', 'provider_payment_id']
|
183
189
|
ordering_fields = ['created_at', 'updated_at', 'amount_usd', 'expires_at']
|
184
190
|
|
@@ -40,9 +40,9 @@ class SubscriptionViewSet(PaymentBaseViewSet):
|
|
40
40
|
|
41
41
|
queryset = Subscription.objects.all()
|
42
42
|
serializer_class = SubscriptionSerializer
|
43
|
-
permission_classes = [permissions.
|
44
|
-
filterset_fields = ['status', 'tier', '
|
45
|
-
search_fields = ['user__username', 'user__email'
|
43
|
+
permission_classes = [permissions.IsAuthenticated] # Allow authenticated users
|
44
|
+
filterset_fields = ['status', 'tier', 'user']
|
45
|
+
search_fields = ['user__username', 'user__email']
|
46
46
|
ordering_fields = ['created_at', 'updated_at', 'expires_at', 'total_requests']
|
47
47
|
|
48
48
|
serializer_classes = {
|
@@ -55,11 +55,17 @@ class SubscriptionViewSet(PaymentBaseViewSet):
|
|
55
55
|
|
56
56
|
def get_queryset(self):
|
57
57
|
"""Optimized queryset with related objects."""
|
58
|
-
|
59
|
-
'user'
|
58
|
+
queryset = super().get_queryset().select_related(
|
59
|
+
'user'
|
60
60
|
).prefetch_related(
|
61
|
-
'
|
61
|
+
'endpoint_groups'
|
62
62
|
)
|
63
|
+
|
64
|
+
# Non-staff users can only see their own subscriptions
|
65
|
+
if not self.request.user.is_staff:
|
66
|
+
queryset = queryset.filter(user=self.request.user)
|
67
|
+
|
68
|
+
return queryset
|
63
69
|
|
64
70
|
@action(detail=True, methods=['post'])
|
65
71
|
def update_status(self, request, pk=None):
|
@@ -161,43 +167,42 @@ class SubscriptionViewSet(PaymentBaseViewSet):
|
|
161
167
|
)
|
162
168
|
|
163
169
|
@action(detail=False, methods=['get'])
|
164
|
-
def
|
170
|
+
def by_tier(self, request):
|
165
171
|
"""
|
166
|
-
Get subscriptions grouped by
|
172
|
+
Get subscriptions grouped by tier.
|
167
173
|
|
168
|
-
GET /api/subscriptions/
|
174
|
+
GET /api/subscriptions/by_tier/
|
169
175
|
"""
|
170
176
|
try:
|
171
177
|
queryset = self.filter_queryset(self.get_queryset())
|
172
178
|
|
173
|
-
|
174
|
-
for
|
175
|
-
|
176
|
-
|
179
|
+
tier_stats = {}
|
180
|
+
for tier_choice in Subscription.SubscriptionTier.choices:
|
181
|
+
tier_code = tier_choice[0]
|
182
|
+
tier_name = tier_choice[1]
|
177
183
|
|
178
|
-
|
179
|
-
tariff_stats[tariff_name] = {
|
180
|
-
'tariff_id': tariff_id,
|
181
|
-
'tariff_name': tariff_name,
|
182
|
-
'total_subscriptions': 0,
|
183
|
-
'active_subscriptions': 0,
|
184
|
-
'total_requests': 0,
|
185
|
-
}
|
184
|
+
tier_subscriptions = queryset.filter(tier=tier_code)
|
186
185
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
186
|
+
tier_stats[tier_code] = {
|
187
|
+
'name': tier_name,
|
188
|
+
'total_subscriptions': tier_subscriptions.count(),
|
189
|
+
'active_subscriptions': tier_subscriptions.filter(
|
190
|
+
status=Subscription.SubscriptionStatus.ACTIVE
|
191
|
+
).count(),
|
192
|
+
'total_requests': tier_subscriptions.aggregate(
|
193
|
+
total=models.Sum('total_requests')
|
194
|
+
)['total'] or 0,
|
195
|
+
}
|
191
196
|
|
192
197
|
return Response({
|
193
|
-
'
|
198
|
+
'tier_stats': tier_stats,
|
194
199
|
'generated_at': timezone.now().isoformat()
|
195
200
|
})
|
196
201
|
|
197
202
|
except Exception as e:
|
198
|
-
logger.error(f"Subscription
|
203
|
+
logger.error(f"Subscription tier stats failed: {e}")
|
199
204
|
return Response(
|
200
|
-
{'error': f'
|
205
|
+
{'error': f'Tier stats failed: {e}'},
|
201
206
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
202
207
|
)
|
203
208
|
|
@@ -212,8 +217,8 @@ class UserSubscriptionViewSet(NestedPaymentViewSet):
|
|
212
217
|
queryset = Subscription.objects.all()
|
213
218
|
serializer_class = SubscriptionSerializer
|
214
219
|
permission_classes = [permissions.IsAuthenticated]
|
215
|
-
filterset_fields = ['status', 'tier'
|
216
|
-
search_fields = [
|
220
|
+
filterset_fields = ['status', 'tier']
|
221
|
+
search_fields = []
|
217
222
|
ordering_fields = ['created_at', 'updated_at', 'expires_at']
|
218
223
|
|
219
224
|
# Nested ViewSet configuration
|
@@ -236,7 +241,7 @@ class UserSubscriptionViewSet(NestedPaymentViewSet):
|
|
236
241
|
if str(self.request.user.id) != str(user_id):
|
237
242
|
return queryset.none()
|
238
243
|
|
239
|
-
return queryset.select_related('
|
244
|
+
return queryset.select_related('user').prefetch_related('endpoint_groups')
|
240
245
|
|
241
246
|
@action(detail=True, methods=['post'])
|
242
247
|
def update_status(self, request, user_pk=None, pk=None):
|
@@ -0,0 +1,40 @@
|
|
1
|
+
"""
|
2
|
+
💰 Payments Overview Dashboard
|
3
|
+
|
4
|
+
Overview dashboard for user payment metrics and analytics.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .views import PaymentsDashboardViewSet
|
8
|
+
from .serializers import (
|
9
|
+
PaymentsDashboardOverviewSerializer,
|
10
|
+
PaymentsMetricsSerializer,
|
11
|
+
BalanceOverviewSerializer,
|
12
|
+
SubscriptionOverviewSerializer,
|
13
|
+
APIKeysOverviewSerializer,
|
14
|
+
PaymentsChartResponseSerializer,
|
15
|
+
)
|
16
|
+
from .services import (
|
17
|
+
PaymentsDashboardMetricsService,
|
18
|
+
PaymentsUsageChartService,
|
19
|
+
RecentPaymentsService,
|
20
|
+
PaymentsAnalyticsService,
|
21
|
+
)
|
22
|
+
|
23
|
+
__all__ = [
|
24
|
+
# Views
|
25
|
+
'PaymentsDashboardViewSet',
|
26
|
+
|
27
|
+
# Serializers
|
28
|
+
'PaymentsDashboardOverviewSerializer',
|
29
|
+
'PaymentsMetricsSerializer',
|
30
|
+
'BalanceOverviewSerializer',
|
31
|
+
'SubscriptionOverviewSerializer',
|
32
|
+
'APIKeysOverviewSerializer',
|
33
|
+
'PaymentsChartResponseSerializer',
|
34
|
+
|
35
|
+
# Services
|
36
|
+
'PaymentsDashboardMetricsService',
|
37
|
+
'PaymentsUsageChartService',
|
38
|
+
'RecentPaymentsService',
|
39
|
+
'PaymentsAnalyticsService',
|
40
|
+
]
|