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