django-cfg 1.3.1__py3-none-any.whl → 1.3.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin_interface/old/payments/base.html +175 -0
  3. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +125 -0
  4. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +113 -0
  5. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +35 -0
  6. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +309 -0
  7. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +303 -0
  8. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +382 -0
  9. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +518 -0
  10. django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/components.css +248 -9
  11. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +163 -0
  12. django_cfg/apps/payments/admin_interface/serializers/__init__.py +39 -0
  13. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +149 -0
  14. django_cfg/apps/payments/admin_interface/serializers/webhook_serializers.py +114 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/base.html +55 -90
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/dialog.html +81 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_help_dialog.html +112 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_status.html +175 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +21 -17
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +123 -250
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +170 -269
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +152 -355
  23. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +202 -551
  24. django_cfg/apps/payments/admin_interface/views/__init__.py +25 -14
  25. django_cfg/apps/payments/admin_interface/views/api/__init__.py +20 -0
  26. django_cfg/apps/payments/admin_interface/views/api/payments.py +191 -0
  27. django_cfg/apps/payments/admin_interface/views/api/stats.py +206 -0
  28. django_cfg/apps/payments/admin_interface/views/api/users.py +60 -0
  29. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +257 -0
  30. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +70 -0
  31. django_cfg/apps/payments/admin_interface/views/base.py +114 -0
  32. django_cfg/apps/payments/admin_interface/views/dashboard.py +60 -0
  33. django_cfg/apps/payments/admin_interface/views/forms.py +94 -0
  34. django_cfg/apps/payments/config/helpers.py +2 -2
  35. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +429 -0
  36. django_cfg/apps/payments/management/commands/currency_stats.py +443 -0
  37. django_cfg/apps/payments/management/commands/manage_currencies.py +9 -20
  38. django_cfg/apps/payments/management/commands/manage_providers.py +5 -5
  39. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  40. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  41. django_cfg/apps/payments/middleware/api_access.py +35 -34
  42. django_cfg/apps/payments/migrations/0001_initial.py +1 -1
  43. django_cfg/apps/payments/models/balance.py +5 -2
  44. django_cfg/apps/payments/models/managers/api_key_managers.py +6 -2
  45. django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
  46. django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
  47. django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
  48. django_cfg/apps/payments/models/subscriptions.py +0 -24
  49. django_cfg/apps/payments/services/cache/__init__.py +1 -1
  50. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  51. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  52. django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
  53. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  54. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  55. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  56. django_cfg/apps/payments/services/core/balance_service.py +13 -2
  57. django_cfg/apps/payments/services/core/payment_service.py +49 -22
  58. django_cfg/apps/payments/services/integrations/ngrok_service.py +3 -3
  59. django_cfg/apps/payments/services/providers/registry.py +20 -0
  60. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  61. django_cfg/apps/payments/signals/balance_signals.py +8 -5
  62. django_cfg/apps/payments/static/payments/js/api-client.js +385 -0
  63. django_cfg/apps/payments/static/payments/js/ngrok-status.js +58 -0
  64. django_cfg/apps/payments/static/payments/js/payment-dashboard.js +50 -0
  65. django_cfg/apps/payments/static/payments/js/payment-form.js +175 -0
  66. django_cfg/apps/payments/static/payments/js/payment-list.js +95 -0
  67. django_cfg/apps/payments/static/payments/js/webhook-dashboard.js +154 -0
  68. django_cfg/apps/payments/urls.py +4 -0
  69. django_cfg/apps/payments/urls_admin.py +37 -18
  70. django_cfg/apps/payments/views/api/api_keys.py +14 -0
  71. django_cfg/apps/payments/views/api/base.py +1 -0
  72. django_cfg/apps/payments/views/api/currencies.py +2 -2
  73. django_cfg/apps/payments/views/api/payments.py +11 -5
  74. django_cfg/apps/payments/views/api/subscriptions.py +36 -31
  75. django_cfg/apps/payments/views/overview/__init__.py +40 -0
  76. django_cfg/apps/payments/views/overview/serializers.py +205 -0
  77. django_cfg/apps/payments/views/overview/services.py +439 -0
  78. django_cfg/apps/payments/views/overview/urls.py +27 -0
  79. django_cfg/apps/payments/views/overview/views.py +231 -0
  80. django_cfg/apps/payments/views/serializers/api_keys.py +20 -6
  81. django_cfg/apps/payments/views/serializers/balances.py +5 -8
  82. django_cfg/apps/payments/views/serializers/currencies.py +2 -6
  83. django_cfg/apps/payments/views/serializers/payments.py +37 -32
  84. django_cfg/apps/payments/views/serializers/subscriptions.py +4 -26
  85. django_cfg/apps/urls.py +2 -1
  86. django_cfg/core/config.py +25 -15
  87. django_cfg/core/generation.py +12 -12
  88. django_cfg/core/integration/display/startup.py +1 -1
  89. django_cfg/core/validation.py +4 -4
  90. django_cfg/management/commands/show_config.py +2 -2
  91. django_cfg/management/commands/tree.py +1 -3
  92. django_cfg/middleware/__init__.py +2 -0
  93. django_cfg/middleware/static_nocache.py +55 -0
  94. django_cfg/models/payments.py +13 -15
  95. django_cfg/models/security.py +15 -0
  96. django_cfg/modules/django_ngrok.py +6 -0
  97. django_cfg/modules/django_unfold/dashboard.py +1 -3
  98. django_cfg/utils/smart_defaults.py +51 -5
  99. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
  100. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/RECORD +111 -69
  101. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +0 -38
  102. django_cfg/apps/payments/admin_interface/views/payment_views.py +0 -259
  103. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +0 -37
  104. django_cfg/apps/payments/services/cache/cache_service.py +0 -235
  105. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/loading_spinner.html +0 -0
  106. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/notification.html +0 -0
  107. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/provider_card.html +0 -0
  108. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/currency_converter.html +0 -0
  109. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/payment_status.html +0 -0
  110. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/dashboard.css +0 -0
  111. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/components.js +0 -0
  112. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/utils.js +0 -0
  113. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
  114. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
  115. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Payment Form Component
3
+ * Handles payment creation form functionality with real provider currencies
4
+ */
5
+ function paymentForm() {
6
+ return {
7
+ loading: false,
8
+ loadingCurrencies: false,
9
+ form: {
10
+ user: '',
11
+ amount_usd: '',
12
+ currency_code: '',
13
+ provider: 'nowpayments',
14
+ description: '',
15
+ callback_url: '',
16
+ cancel_url: ''
17
+ },
18
+ currencies: [],
19
+ allCurrencies: [],
20
+ providers: [
21
+ { value: 'nowpayments', name: 'NowPayments', display_name: 'NowPayments' }
22
+ ],
23
+ conversionResult: null,
24
+ users: [],
25
+
26
+ async init() {
27
+ await this.loadInitialData();
28
+ },
29
+
30
+ async loadInitialData() {
31
+ this.loading = true;
32
+ try {
33
+ // Load all currencies and provider-specific currencies
34
+ await Promise.all([
35
+ this.loadAllCurrencies(),
36
+ this.loadProviderCurrencies(),
37
+ this.loadUsers()
38
+ ]);
39
+ } catch (error) {
40
+ console.error('Failed to load initial data:', error);
41
+ PaymentAPI.utils.showNotification('Failed to load form data', 'error');
42
+ } finally {
43
+ this.loading = false;
44
+ }
45
+ },
46
+
47
+ async loadAllCurrencies() {
48
+ try {
49
+ const data = await PaymentAPI.currencies.supported();
50
+ this.allCurrencies = data.currencies || [];
51
+ } catch (error) {
52
+ console.error('Failed to load currencies:', error);
53
+ }
54
+ },
55
+
56
+ async loadProviderCurrencies() {
57
+ if (!this.form.provider) return;
58
+
59
+ this.loadingCurrencies = true;
60
+ try {
61
+ const data = await PaymentAPI.currencies.providerConfigs(this.form.provider);
62
+ this.currencies = data.results || data.currencies || [];
63
+
64
+ // Transform provider currency data for display
65
+ this.currencies = this.currencies.map(pc => ({
66
+ code: pc.currency?.code || pc.provider_currency_code,
67
+ name: pc.currency?.name || pc.provider_currency_code,
68
+ type: pc.currency?.currency_type || 'unknown',
69
+ symbol: pc.currency?.symbol || '',
70
+ network: pc.network?.code || null,
71
+ network_name: pc.network?.name || null,
72
+ min_amount: pc.min_amount,
73
+ max_amount: pc.max_amount,
74
+ fee_percentage: pc.fee_percentage,
75
+ fixed_fee: pc.fixed_fee,
76
+ provider_code: pc.provider_currency_code
77
+ }));
78
+
79
+ // If current currency is not supported by provider, reset it
80
+ if (this.form.currency_code && !this.currencies.find(c => c.code === this.form.currency_code)) {
81
+ this.form.currency_code = '';
82
+ this.conversionResult = null;
83
+ }
84
+ } catch (error) {
85
+ console.error('Failed to load provider currencies:', error);
86
+ this.currencies = [];
87
+ } finally {
88
+ this.loadingCurrencies = false;
89
+ }
90
+ },
91
+
92
+ async loadUsers() {
93
+ try {
94
+ const data = await PaymentAPI.admin.users.list();
95
+ this.users = data.results || data || [];
96
+
97
+ // If no users loaded, try to get current user info
98
+ if (this.users.length === 0) {
99
+ console.warn('No users loaded from admin API');
100
+ this.users = [{ id: '', username: 'Select User', email: '' }];
101
+ }
102
+ } catch (error) {
103
+ console.error('Failed to load users:', error);
104
+ // Set empty option for user selection
105
+ this.users = [{ id: '', username: 'Select User', email: '' }];
106
+ }
107
+ },
108
+
109
+ async onProviderChange() {
110
+ await this.loadProviderCurrencies();
111
+ },
112
+
113
+ async onAmountOrCurrencyChange() {
114
+ if (this.form.amount_usd && this.form.currency_code && this.form.currency_code !== 'USD') {
115
+ await this.convertCurrency();
116
+ } else {
117
+ this.conversionResult = null;
118
+ }
119
+ },
120
+
121
+ async convertCurrency() {
122
+ if (!this.form.amount_usd || !this.form.currency_code) return;
123
+
124
+ try {
125
+ const result = await PaymentAPI.currencies.convert('USD', this.form.currency_code, this.form.amount_usd);
126
+ this.conversionResult = {
127
+ amount: result.converted_amount,
128
+ rate: result.rate,
129
+ currency: this.form.currency_code
130
+ };
131
+ } catch (error) {
132
+ console.error('Currency conversion failed:', error);
133
+ this.conversionResult = null;
134
+ }
135
+ },
136
+
137
+ getCurrencyInfo(code) {
138
+ return this.currencies.find(c => c.code === code) ||
139
+ this.allCurrencies.find(c => c.code === code) ||
140
+ { code, name: code, type: 'unknown' };
141
+ },
142
+
143
+ validateForm() {
144
+ const errors = [];
145
+
146
+ if (!this.form.user) errors.push('User is required');
147
+ if (!this.form.amount_usd || this.form.amount_usd <= 0) errors.push('Valid amount is required');
148
+ if (!this.form.currency_code) errors.push('Currency is required');
149
+ if (!this.form.provider) errors.push('Provider is required');
150
+
151
+ return errors;
152
+ },
153
+
154
+ async submitForm() {
155
+ const errors = this.validateForm();
156
+ if (errors.length > 0) {
157
+ PaymentAPI.utils.showNotification(errors.join(', '), 'error');
158
+ return;
159
+ }
160
+
161
+ this.loading = true;
162
+
163
+ try {
164
+ const data = await PaymentAPI.admin.payments.create(this.form);
165
+ PaymentAPI.utils.showNotification('Payment created successfully!', 'success');
166
+ window.location.href = `/cfg/admin/django_cfg_payments/admin/payments/${data.id}/`;
167
+ } catch (error) {
168
+ console.error('Error:', error);
169
+ PaymentAPI.utils.showNotification(error.message || 'Failed to create payment', 'error');
170
+ } finally {
171
+ this.loading = false;
172
+ }
173
+ }
174
+ };
175
+ }
@@ -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
+ }
@@ -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
- WebhookDashboardView,
14
+ PaymentDashboardView,
13
15
  PaymentFormView,
14
- PaymentStatusView,
16
+ PaymentDetailView,
15
17
  PaymentListView,
16
- PaymentDashboardView,
17
- CurrencyConverterView,
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
- # Main dashboard
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-create'),
31
- path('<uuid:pk>/', staff_member_required(PaymentStatusView.as_view()), name='payment-detail'),
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
- # Tools and utilities
42
- path('tools/', include([
43
- path('converter/', staff_member_required(CurrencyConverterView.as_view()), name='currency-converter'),
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 = ['currency', 'is_active']
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', 'currency', 'network', 'is_enabled']
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.IsAdminUser] # Admin only for global access
41
- filterset_fields = ['status', 'provider', 'currency_code', 'user']
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
- return super().get_queryset().select_related('user').prefetch_related(
56
- 'user__userbalance_set'
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', 'currency_code']
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