django-cfg 1.2.27__py3-none-any.whl → 1.2.31__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/__init__.py +3 -2
- django_cfg/apps/payments/admin/balance_admin.py +18 -18
- django_cfg/apps/payments/admin/currencies_admin.py +319 -131
- django_cfg/apps/payments/admin/payments_admin.py +15 -4
- django_cfg/apps/payments/config/module.py +2 -2
- django_cfg/apps/payments/config/utils.py +2 -2
- django_cfg/apps/payments/decorators.py +2 -2
- django_cfg/apps/payments/management/commands/README.md +95 -127
- django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
- django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
- django_cfg/apps/payments/managers/__init__.py +3 -2
- django_cfg/apps/payments/managers/balance_manager.py +2 -2
- django_cfg/apps/payments/managers/currency_manager.py +272 -49
- django_cfg/apps/payments/managers/payment_manager.py +161 -13
- django_cfg/apps/payments/middleware/api_access.py +2 -2
- django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
- django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
- django_cfg/apps/payments/models/__init__.py +3 -2
- django_cfg/apps/payments/models/currencies.py +187 -71
- django_cfg/apps/payments/models/payments.py +3 -2
- django_cfg/apps/payments/serializers/__init__.py +3 -2
- django_cfg/apps/payments/serializers/currencies.py +20 -12
- django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
- django_cfg/apps/payments/services/core/balance_service.py +2 -2
- django_cfg/apps/payments/services/core/fallback_service.py +2 -2
- django_cfg/apps/payments/services/core/payment_service.py +3 -6
- django_cfg/apps/payments/services/core/subscription_service.py +4 -7
- django_cfg/apps/payments/services/internal_types.py +171 -7
- django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
- django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
- django_cfg/apps/payments/services/providers/base.py +144 -43
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
- django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
- django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
- django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
- django_cfg/apps/payments/services/providers/registry.py +294 -11
- django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
- django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
- django_cfg/apps/payments/services/security/error_handler.py +6 -8
- django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
- django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
- django_cfg/apps/payments/signals/api_key_signals.py +2 -2
- django_cfg/apps/payments/signals/payment_signals.py +11 -5
- django_cfg/apps/payments/signals/subscription_signals.py +2 -2
- django_cfg/apps/payments/static/payments/css/payments.css +340 -0
- django_cfg/apps/payments/static/payments/js/notifications.js +202 -0
- django_cfg/apps/payments/static/payments/js/payment-utils.js +318 -0
- django_cfg/apps/payments/static/payments/js/theme.js +86 -0
- django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
- django_cfg/apps/payments/templates/payments/base.html +182 -0
- django_cfg/apps/payments/templates/payments/components/payment_card.html +201 -0
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +109 -0
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +43 -0
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +40 -0
- django_cfg/apps/payments/templates/payments/components/status_badge.html +34 -0
- django_cfg/apps/payments/templates/payments/components/status_overview.html +148 -0
- django_cfg/apps/payments/templates/payments/dashboard.html +258 -0
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
- django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
- django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
- django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
- django_cfg/apps/payments/templates/payments/stats.html +261 -0
- django_cfg/apps/payments/templates/payments/test.html +213 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -0
- django_cfg/apps/payments/templatetags/payments_tags.py +315 -0
- django_cfg/apps/payments/urls.py +3 -1
- django_cfg/apps/payments/urls_admin.py +58 -0
- django_cfg/apps/payments/utils/__init__.py +1 -3
- django_cfg/apps/payments/utils/billing_utils.py +2 -2
- django_cfg/apps/payments/utils/config_utils.py +2 -8
- django_cfg/apps/payments/utils/validation_utils.py +2 -2
- django_cfg/apps/payments/views/__init__.py +3 -2
- django_cfg/apps/payments/views/currency_views.py +31 -20
- django_cfg/apps/payments/views/payment_views.py +2 -2
- django_cfg/apps/payments/views/templates/__init__.py +25 -0
- django_cfg/apps/payments/views/templates/ajax.py +451 -0
- django_cfg/apps/payments/views/templates/base.py +212 -0
- django_cfg/apps/payments/views/templates/dashboard.py +60 -0
- django_cfg/apps/payments/views/templates/payment_detail.py +102 -0
- django_cfg/apps/payments/views/templates/payment_management.py +158 -0
- django_cfg/apps/payments/views/templates/qr_code.py +174 -0
- django_cfg/apps/payments/views/templates/stats.py +244 -0
- django_cfg/apps/payments/views/templates/utils.py +181 -0
- django_cfg/apps/payments/views/webhook_views.py +2 -2
- django_cfg/apps/payments/viewsets.py +3 -2
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +6 -3
- django_cfg/core/config.py +35 -0
- django_cfg/models/payments.py +2 -8
- django_cfg/modules/django_currency/__init__.py +16 -11
- django_cfg/modules/django_currency/clients/__init__.py +4 -4
- django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
- django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
- django_cfg/modules/django_currency/core/__init__.py +1 -7
- django_cfg/modules/django_currency/core/converter.py +18 -23
- django_cfg/modules/django_currency/core/models.py +122 -11
- django_cfg/modules/django_currency/database/__init__.py +4 -4
- django_cfg/modules/django_currency/database/database_loader.py +190 -309
- django_cfg/modules/django_unfold/dashboard.py +7 -2
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/.gitignore +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/admin/components/action_grid.html +9 -9
- django_cfg/templates/admin/components/metric_card.html +5 -5
- django_cfg/templates/admin/components/status_badge.html +2 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
- django_cfg/templates/admin/snippets/components/system_health.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
- {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/METADATA +13 -18
- {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/RECORD +130 -83
- django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
- django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -310
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
"""
|
2
|
+
QR code views for crypto payments.
|
3
|
+
|
4
|
+
Provides QR code generation and display for cryptocurrency payments.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.views.generic import DetailView
|
8
|
+
from django.http import JsonResponse
|
9
|
+
from .base import (
|
10
|
+
SuperuserRequiredMixin,
|
11
|
+
PaymentContextMixin,
|
12
|
+
superuser_required,
|
13
|
+
log_view_access
|
14
|
+
)
|
15
|
+
from ...models import UniversalPayment
|
16
|
+
|
17
|
+
|
18
|
+
class PaymentQRCodeView(
|
19
|
+
SuperuserRequiredMixin,
|
20
|
+
PaymentContextMixin,
|
21
|
+
DetailView
|
22
|
+
):
|
23
|
+
"""QR code view for crypto payments."""
|
24
|
+
|
25
|
+
model = UniversalPayment
|
26
|
+
template_name = 'payments/payment_qr.html'
|
27
|
+
context_object_name = 'payment'
|
28
|
+
page_title = 'Payment QR Code'
|
29
|
+
|
30
|
+
def get_breadcrumbs(self):
|
31
|
+
payment = self.get_object()
|
32
|
+
return [
|
33
|
+
{'name': 'Dashboard', 'url': '/payments/admin/'},
|
34
|
+
{'name': 'Payments', 'url': '/payments/admin/list/'},
|
35
|
+
{'name': f'Payment #{payment.internal_payment_id or str(payment.id)[:8]}',
|
36
|
+
'url': f'/payments/admin/payment/{payment.id}/'},
|
37
|
+
{'name': 'QR Code', 'url': ''},
|
38
|
+
]
|
39
|
+
|
40
|
+
def get_context_data(self, **kwargs):
|
41
|
+
context = super().get_context_data(**kwargs)
|
42
|
+
payment = self.get_object()
|
43
|
+
|
44
|
+
# Log access for audit
|
45
|
+
log_view_access('payment_qr', self.request.user, payment_id=payment.id)
|
46
|
+
|
47
|
+
# Check if payment supports QR codes
|
48
|
+
if not self._supports_qr_code(payment):
|
49
|
+
context['error'] = "QR codes are not supported for this payment method"
|
50
|
+
return context
|
51
|
+
|
52
|
+
# Generate QR code data
|
53
|
+
qr_data = self._generate_qr_data(payment)
|
54
|
+
|
55
|
+
# Get payment instructions
|
56
|
+
instructions = self._get_payment_instructions(payment)
|
57
|
+
|
58
|
+
# Get common context
|
59
|
+
common_context = self.get_common_context()
|
60
|
+
|
61
|
+
context.update({
|
62
|
+
'qr_data': qr_data,
|
63
|
+
'qr_size': self.request.GET.get('size', 256),
|
64
|
+
'instructions': instructions,
|
65
|
+
'is_crypto': self._is_crypto_payment(payment),
|
66
|
+
'can_copy': True,
|
67
|
+
**common_context
|
68
|
+
})
|
69
|
+
|
70
|
+
return context
|
71
|
+
|
72
|
+
def _supports_qr_code(self, payment):
|
73
|
+
"""Check if payment method supports QR codes."""
|
74
|
+
crypto_providers = ['nowpayments', 'cryptapi', 'cryptomus']
|
75
|
+
return payment.provider in crypto_providers and payment.pay_address
|
76
|
+
|
77
|
+
def _is_crypto_payment(self, payment):
|
78
|
+
"""Check if payment is cryptocurrency-based."""
|
79
|
+
crypto_providers = ['nowpayments', 'cryptapi', 'cryptomus']
|
80
|
+
return payment.provider in crypto_providers
|
81
|
+
|
82
|
+
def _generate_qr_data(self, payment):
|
83
|
+
"""Generate QR code data for the payment."""
|
84
|
+
if not payment.pay_address:
|
85
|
+
return None
|
86
|
+
|
87
|
+
# For crypto payments, use standard format
|
88
|
+
if self._is_crypto_payment(payment):
|
89
|
+
qr_data = payment.pay_address
|
90
|
+
|
91
|
+
# Add amount if available
|
92
|
+
if payment.pay_amount:
|
93
|
+
# Use appropriate URI scheme based on currency
|
94
|
+
uri_schemes = {
|
95
|
+
'BTC': 'bitcoin',
|
96
|
+
'LTC': 'litecoin',
|
97
|
+
'ETH': 'ethereum',
|
98
|
+
'BCH': 'bitcoincash',
|
99
|
+
}
|
100
|
+
|
101
|
+
scheme = uri_schemes.get(payment.currency_code.upper(), 'crypto')
|
102
|
+
qr_data = f"{scheme}:{payment.pay_address}?amount={payment.pay_amount}"
|
103
|
+
|
104
|
+
# Add label if available
|
105
|
+
if payment.internal_payment_id:
|
106
|
+
qr_data += f"&label=Payment%20{payment.internal_payment_id}"
|
107
|
+
|
108
|
+
return qr_data
|
109
|
+
|
110
|
+
return payment.pay_address
|
111
|
+
|
112
|
+
def _get_payment_instructions(self, payment):
|
113
|
+
"""Get step-by-step payment instructions."""
|
114
|
+
if not self._is_crypto_payment(payment):
|
115
|
+
return []
|
116
|
+
|
117
|
+
instructions = [
|
118
|
+
"Scan the QR code with your crypto wallet app",
|
119
|
+
f"Send exactly {payment.pay_amount} {payment.currency_code} to the address",
|
120
|
+
"Wait for network confirmations",
|
121
|
+
"Payment will be automatically confirmed"
|
122
|
+
]
|
123
|
+
|
124
|
+
# Add provider-specific instructions
|
125
|
+
if payment.provider == 'cryptapi':
|
126
|
+
instructions.append("Minimum 1 confirmation required")
|
127
|
+
elif payment.provider == 'nowpayments':
|
128
|
+
instructions.append("Minimum 2 confirmations required")
|
129
|
+
elif payment.provider == 'cryptomus':
|
130
|
+
instructions.append("Confirmations depend on selected network")
|
131
|
+
|
132
|
+
return instructions
|
133
|
+
|
134
|
+
|
135
|
+
@superuser_required
|
136
|
+
def qr_code_data_ajax(request, payment_id):
|
137
|
+
"""AJAX endpoint to get QR code data."""
|
138
|
+
try:
|
139
|
+
payment = UniversalPayment.objects.get(id=payment_id)
|
140
|
+
|
141
|
+
# Log access for audit
|
142
|
+
log_view_access('qr_ajax', request.user, payment_id=payment_id)
|
143
|
+
|
144
|
+
view = PaymentQRCodeView()
|
145
|
+
|
146
|
+
# Check if payment supports QR codes
|
147
|
+
if not view._supports_qr_code(payment):
|
148
|
+
return JsonResponse({
|
149
|
+
'error': 'QR codes not supported for this payment method'
|
150
|
+
}, status=400)
|
151
|
+
|
152
|
+
# Generate QR data
|
153
|
+
qr_data = view._generate_qr_data(payment)
|
154
|
+
|
155
|
+
if not qr_data:
|
156
|
+
return JsonResponse({
|
157
|
+
'error': 'Unable to generate QR code data'
|
158
|
+
}, status=400)
|
159
|
+
|
160
|
+
response_data = {
|
161
|
+
'qr_data': qr_data,
|
162
|
+
'payment_address': payment.pay_address,
|
163
|
+
'payment_amount': str(payment.pay_amount) if payment.pay_amount else None,
|
164
|
+
'currency': payment.currency_code,
|
165
|
+
'provider': payment.provider,
|
166
|
+
'instructions': view._get_payment_instructions(payment),
|
167
|
+
}
|
168
|
+
|
169
|
+
return JsonResponse(response_data)
|
170
|
+
|
171
|
+
except UniversalPayment.DoesNotExist:
|
172
|
+
return JsonResponse({'error': 'Payment not found'}, status=404)
|
173
|
+
except Exception as e:
|
174
|
+
return JsonResponse({'error': str(e)}, status=500)
|
@@ -0,0 +1,244 @@
|
|
1
|
+
"""
|
2
|
+
Payment statistics and analytics views.
|
3
|
+
|
4
|
+
Provides comprehensive analytics for payment performance and trends.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.views.generic import TemplateView
|
8
|
+
from django.utils import timezone
|
9
|
+
from django.db.models import Q
|
10
|
+
from datetime import timedelta
|
11
|
+
from .base import (
|
12
|
+
SuperuserRequiredMixin,
|
13
|
+
PaymentStatsMixin,
|
14
|
+
PaymentContextMixin,
|
15
|
+
log_view_access
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class PaymentStatsView(
|
20
|
+
SuperuserRequiredMixin,
|
21
|
+
PaymentStatsMixin,
|
22
|
+
PaymentContextMixin,
|
23
|
+
TemplateView
|
24
|
+
):
|
25
|
+
"""Analytics and statistics view."""
|
26
|
+
|
27
|
+
template_name = 'payments/stats.html'
|
28
|
+
page_title = 'Payment Analytics'
|
29
|
+
|
30
|
+
def get_breadcrumbs(self):
|
31
|
+
return [
|
32
|
+
{'name': 'Dashboard', 'url': '/payments/admin/'},
|
33
|
+
{'name': 'Analytics', 'url': ''},
|
34
|
+
]
|
35
|
+
|
36
|
+
def get_context_data(self, **kwargs):
|
37
|
+
context = super().get_context_data(**kwargs)
|
38
|
+
|
39
|
+
# Log access for audit
|
40
|
+
log_view_access('payment_stats', self.request.user)
|
41
|
+
|
42
|
+
# Get time period from request (default to 30 days)
|
43
|
+
days = int(self.request.GET.get('days', 30))
|
44
|
+
|
45
|
+
# Calculate date ranges
|
46
|
+
now = timezone.now()
|
47
|
+
ranges = self._get_date_ranges(now, days)
|
48
|
+
|
49
|
+
# Get comprehensive statistics
|
50
|
+
stats = {
|
51
|
+
'overview': self._get_overview_stats(),
|
52
|
+
'time_periods': self._get_time_period_stats(ranges),
|
53
|
+
'providers': self._get_detailed_provider_stats(),
|
54
|
+
'status_distribution': self._get_status_distribution(),
|
55
|
+
'trends': self._get_trend_data(days),
|
56
|
+
'performance': self._get_performance_metrics(),
|
57
|
+
}
|
58
|
+
|
59
|
+
# Get common context
|
60
|
+
common_context = self.get_common_context()
|
61
|
+
|
62
|
+
context.update({
|
63
|
+
'stats': stats,
|
64
|
+
'selected_days': days,
|
65
|
+
'available_periods': [7, 30, 90, 365],
|
66
|
+
'date_ranges': ranges,
|
67
|
+
**common_context
|
68
|
+
})
|
69
|
+
|
70
|
+
return context
|
71
|
+
|
72
|
+
def _get_date_ranges(self, now, days):
|
73
|
+
"""Calculate various date ranges for statistics."""
|
74
|
+
return {
|
75
|
+
'current_period_start': now - timedelta(days=days),
|
76
|
+
'current_period_end': now,
|
77
|
+
'previous_period_start': now - timedelta(days=days * 2),
|
78
|
+
'previous_period_end': now - timedelta(days=days),
|
79
|
+
'last_7_days': now - timedelta(days=7),
|
80
|
+
'last_30_days': now - timedelta(days=30),
|
81
|
+
'last_year': now - timedelta(days=365),
|
82
|
+
}
|
83
|
+
|
84
|
+
def _get_overview_stats(self):
|
85
|
+
"""Get overall payment statistics."""
|
86
|
+
return self.get_payment_stats()
|
87
|
+
|
88
|
+
def _get_time_period_stats(self, ranges):
|
89
|
+
"""Get statistics for different time periods."""
|
90
|
+
from ...models import UniversalPayment
|
91
|
+
|
92
|
+
periods = {}
|
93
|
+
|
94
|
+
# Current period
|
95
|
+
current_qs = UniversalPayment.objects.filter(
|
96
|
+
created_at__gte=ranges['current_period_start'],
|
97
|
+
created_at__lte=ranges['current_period_end']
|
98
|
+
)
|
99
|
+
periods['current'] = self.get_payment_stats(current_qs)
|
100
|
+
|
101
|
+
# Previous period for comparison
|
102
|
+
previous_qs = UniversalPayment.objects.filter(
|
103
|
+
created_at__gte=ranges['previous_period_start'],
|
104
|
+
created_at__lte=ranges['previous_period_end']
|
105
|
+
)
|
106
|
+
periods['previous'] = self.get_payment_stats(previous_qs)
|
107
|
+
|
108
|
+
# Calculate growth rates
|
109
|
+
periods['growth'] = self._calculate_growth_rates(
|
110
|
+
periods['current'],
|
111
|
+
periods['previous']
|
112
|
+
)
|
113
|
+
|
114
|
+
# Last 7 days
|
115
|
+
last_7_qs = UniversalPayment.objects.filter(created_at__gte=ranges['last_7_days'])
|
116
|
+
periods['last_7_days'] = self.get_payment_stats(last_7_qs)
|
117
|
+
|
118
|
+
# Last 30 days
|
119
|
+
last_30_qs = UniversalPayment.objects.filter(created_at__gte=ranges['last_30_days'])
|
120
|
+
periods['last_30_days'] = self.get_payment_stats(last_30_qs)
|
121
|
+
|
122
|
+
return periods
|
123
|
+
|
124
|
+
def _get_detailed_provider_stats(self):
|
125
|
+
"""Get detailed provider statistics."""
|
126
|
+
provider_stats = self.get_provider_stats()
|
127
|
+
|
128
|
+
# Add additional metrics for each provider
|
129
|
+
for stat in provider_stats:
|
130
|
+
stat['avg_amount'] = stat['volume'] / stat['count'] if stat['count'] > 0 else 0
|
131
|
+
stat['failure_rate'] = 100 - stat['success_rate']
|
132
|
+
|
133
|
+
return provider_stats
|
134
|
+
|
135
|
+
def _get_status_distribution(self):
|
136
|
+
"""Get payment status distribution."""
|
137
|
+
from ...models import UniversalPayment
|
138
|
+
from django.db.models import Count
|
139
|
+
|
140
|
+
distribution = UniversalPayment.objects.values('status').annotate(
|
141
|
+
count=Count('id')
|
142
|
+
).order_by('-count')
|
143
|
+
|
144
|
+
total = sum(item['count'] for item in distribution)
|
145
|
+
|
146
|
+
# Add percentage
|
147
|
+
for item in distribution:
|
148
|
+
item['percentage'] = (item['count'] / total * 100) if total > 0 else 0
|
149
|
+
|
150
|
+
return distribution
|
151
|
+
|
152
|
+
def _get_trend_data(self, days):
|
153
|
+
"""Get trend data for charts."""
|
154
|
+
from ...models import UniversalPayment
|
155
|
+
from django.db.models import Count, Sum
|
156
|
+
from django.db.models.functions import TruncDate
|
157
|
+
|
158
|
+
end_date = timezone.now()
|
159
|
+
start_date = end_date - timedelta(days=days)
|
160
|
+
|
161
|
+
# Daily trends
|
162
|
+
daily_trends = UniversalPayment.objects.filter(
|
163
|
+
created_at__gte=start_date,
|
164
|
+
created_at__lte=end_date
|
165
|
+
).annotate(
|
166
|
+
date=TruncDate('created_at')
|
167
|
+
).values('date').annotate(
|
168
|
+
count=Count('id'),
|
169
|
+
volume=Sum('amount_usd'),
|
170
|
+
completed=Count('id', filter=Q(status='completed'))
|
171
|
+
).order_by('date')
|
172
|
+
|
173
|
+
# Convert to list and add calculated fields
|
174
|
+
trends = []
|
175
|
+
for item in daily_trends:
|
176
|
+
trends.append({
|
177
|
+
'date': item['date'].isoformat(),
|
178
|
+
'count': item['count'],
|
179
|
+
'volume': float(item['volume'] or 0),
|
180
|
+
'completed': item['completed'],
|
181
|
+
'success_rate': (item['completed'] / item['count'] * 100) if item['count'] > 0 else 0
|
182
|
+
})
|
183
|
+
|
184
|
+
return trends
|
185
|
+
|
186
|
+
def _get_performance_metrics(self):
|
187
|
+
"""Get performance metrics."""
|
188
|
+
from ...models import UniversalPayment, PaymentEvent
|
189
|
+
from django.db.models import Avg, Min, Max
|
190
|
+
|
191
|
+
# Average processing time (from created to completed)
|
192
|
+
completed_payments = UniversalPayment.objects.filter(
|
193
|
+
status='completed',
|
194
|
+
completed_at__isnull=False
|
195
|
+
)
|
196
|
+
|
197
|
+
processing_times = []
|
198
|
+
for payment in completed_payments[:100]: # Sample for performance
|
199
|
+
if payment.completed_at and payment.created_at:
|
200
|
+
duration = payment.completed_at - payment.created_at
|
201
|
+
processing_times.append(duration.total_seconds())
|
202
|
+
|
203
|
+
metrics = {
|
204
|
+
'avg_processing_time': sum(processing_times) / len(processing_times) if processing_times else 0,
|
205
|
+
'min_processing_time': min(processing_times) if processing_times else 0,
|
206
|
+
'max_processing_time': max(processing_times) if processing_times else 0,
|
207
|
+
}
|
208
|
+
|
209
|
+
# Convert seconds to human readable format
|
210
|
+
formatted_metrics = {}
|
211
|
+
for key, value in metrics.items():
|
212
|
+
if value > 0:
|
213
|
+
formatted_metrics[f"{key}_formatted"] = self._format_duration(value)
|
214
|
+
else:
|
215
|
+
formatted_metrics[f"{key}_formatted"] = "N/A"
|
216
|
+
|
217
|
+
# Add formatted metrics to the original metrics
|
218
|
+
metrics.update(formatted_metrics)
|
219
|
+
|
220
|
+
return metrics
|
221
|
+
|
222
|
+
def _calculate_growth_rates(self, current, previous):
|
223
|
+
"""Calculate growth rates between two periods."""
|
224
|
+
growth = {}
|
225
|
+
|
226
|
+
for key in ['total_count', 'total_volume', 'completed_count']:
|
227
|
+
current_val = current.get(key, 0)
|
228
|
+
previous_val = previous.get(key, 0)
|
229
|
+
|
230
|
+
if previous_val > 0:
|
231
|
+
growth[f"{key}_rate"] = ((current_val - previous_val) / previous_val) * 100
|
232
|
+
else:
|
233
|
+
growth[f"{key}_rate"] = 100 if current_val > 0 else 0
|
234
|
+
|
235
|
+
return growth
|
236
|
+
|
237
|
+
def _format_duration(self, seconds):
|
238
|
+
"""Format duration in seconds to human readable format."""
|
239
|
+
if seconds < 60:
|
240
|
+
return f"{seconds:.1f}s"
|
241
|
+
elif seconds < 3600:
|
242
|
+
return f"{seconds/60:.1f}m"
|
243
|
+
else:
|
244
|
+
return f"{seconds/3600:.1f}h"
|
@@ -0,0 +1,181 @@
|
|
1
|
+
"""
|
2
|
+
Utility views for payment dashboard.
|
3
|
+
|
4
|
+
Provides testing, debugging, and development functionality.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.views.generic import TemplateView
|
8
|
+
from django.utils import timezone
|
9
|
+
from datetime import timedelta
|
10
|
+
from .base import (
|
11
|
+
SuperuserRequiredMixin,
|
12
|
+
PaymentContextMixin,
|
13
|
+
log_view_access
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class PaymentTestView(
|
18
|
+
SuperuserRequiredMixin,
|
19
|
+
PaymentContextMixin,
|
20
|
+
TemplateView
|
21
|
+
):
|
22
|
+
"""Test view for development and debugging purposes."""
|
23
|
+
|
24
|
+
template_name = 'payments/test.html'
|
25
|
+
page_title = 'Payment System Test'
|
26
|
+
|
27
|
+
def get_breadcrumbs(self):
|
28
|
+
return [
|
29
|
+
{'name': 'Dashboard', 'url': '/payments/admin/'},
|
30
|
+
{'name': 'System Test', 'url': ''},
|
31
|
+
]
|
32
|
+
|
33
|
+
def get_context_data(self, **kwargs):
|
34
|
+
context = super().get_context_data(**kwargs)
|
35
|
+
|
36
|
+
# Log access for audit
|
37
|
+
log_view_access('payment_test', self.request.user)
|
38
|
+
|
39
|
+
# Create sample data for testing templates
|
40
|
+
sample_data = self._generate_sample_data()
|
41
|
+
|
42
|
+
# Get system information
|
43
|
+
system_info = self._get_system_info()
|
44
|
+
|
45
|
+
# Get test scenarios
|
46
|
+
test_scenarios = self._get_test_scenarios()
|
47
|
+
|
48
|
+
# Get common context
|
49
|
+
common_context = self.get_common_context()
|
50
|
+
|
51
|
+
context.update({
|
52
|
+
'sample_data': sample_data,
|
53
|
+
'system_info': system_info,
|
54
|
+
'test_scenarios': test_scenarios,
|
55
|
+
'test_mode': True,
|
56
|
+
**common_context
|
57
|
+
})
|
58
|
+
|
59
|
+
return context
|
60
|
+
|
61
|
+
def _generate_sample_data(self):
|
62
|
+
"""Generate sample payment data for testing."""
|
63
|
+
sample_payments = []
|
64
|
+
statuses = ['pending', 'confirming', 'completed', 'failed']
|
65
|
+
providers = ['nowpayments', 'cryptapi', 'cryptomus', 'stripe']
|
66
|
+
|
67
|
+
for i in range(12):
|
68
|
+
sample_payments.append({
|
69
|
+
'id': f'sample-{i}',
|
70
|
+
'internal_payment_id': f'PAY-{1000 + i}',
|
71
|
+
'provider_payment_id': f'PROV-{2000 + i}',
|
72
|
+
'amount_usd': 50.0 + (i * 25),
|
73
|
+
'currency_code': 'USD',
|
74
|
+
'status': statuses[i % len(statuses)],
|
75
|
+
'provider': providers[i % len(providers)],
|
76
|
+
'user_email': f'user{i}@example.com',
|
77
|
+
'created_at': timezone.now() - timedelta(hours=i),
|
78
|
+
'updated_at': timezone.now() - timedelta(minutes=i * 10),
|
79
|
+
'pay_address': f'1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa{i}' if i % 2 == 0 else None,
|
80
|
+
'pay_amount': 0.001 + (i * 0.0001) if i % 2 == 0 else None,
|
81
|
+
})
|
82
|
+
|
83
|
+
return {
|
84
|
+
'payments': sample_payments,
|
85
|
+
'stats': {
|
86
|
+
'total_count': len(sample_payments),
|
87
|
+
'pending_count': len([p for p in sample_payments if p['status'] == 'pending']),
|
88
|
+
'completed_count': len([p for p in sample_payments if p['status'] == 'completed']),
|
89
|
+
'failed_count': len([p for p in sample_payments if p['status'] == 'failed']),
|
90
|
+
'total_volume': sum(p['amount_usd'] for p in sample_payments),
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
def _get_system_info(self):
|
95
|
+
"""Get system information for debugging."""
|
96
|
+
import django
|
97
|
+
import sys
|
98
|
+
from django.conf import settings
|
99
|
+
|
100
|
+
info = {
|
101
|
+
'django_version': django.get_version(),
|
102
|
+
'python_version': sys.version,
|
103
|
+
'debug_mode': settings.DEBUG,
|
104
|
+
'database_engine': settings.DATABASES['default']['ENGINE'],
|
105
|
+
'installed_apps': len(settings.INSTALLED_APPS),
|
106
|
+
'timezone': str(settings.TIME_ZONE),
|
107
|
+
'language': settings.LANGUAGE_CODE,
|
108
|
+
}
|
109
|
+
|
110
|
+
# Add payment-specific info
|
111
|
+
try:
|
112
|
+
from ...models import UniversalPayment, PaymentEvent
|
113
|
+
info.update({
|
114
|
+
'total_payments': UniversalPayment.objects.count(),
|
115
|
+
'total_events': PaymentEvent.objects.count(),
|
116
|
+
'providers_in_use': list(
|
117
|
+
UniversalPayment.objects.values_list('provider', flat=True).distinct()
|
118
|
+
),
|
119
|
+
})
|
120
|
+
except Exception:
|
121
|
+
info.update({
|
122
|
+
'total_payments': 'Unable to query',
|
123
|
+
'total_events': 'Unable to query',
|
124
|
+
'providers_in_use': [],
|
125
|
+
})
|
126
|
+
|
127
|
+
return info
|
128
|
+
|
129
|
+
def _get_test_scenarios(self):
|
130
|
+
"""Get available test scenarios."""
|
131
|
+
scenarios = [
|
132
|
+
{
|
133
|
+
'name': 'Template Component Test',
|
134
|
+
'description': 'Test all payment template components with sample data',
|
135
|
+
'endpoint': '/payments/test/?test=components',
|
136
|
+
'available': True,
|
137
|
+
},
|
138
|
+
{
|
139
|
+
'name': 'Status Badge Test',
|
140
|
+
'description': 'Test payment status badges for all possible statuses',
|
141
|
+
'endpoint': '/payments/test/?test=status_badges',
|
142
|
+
'available': True,
|
143
|
+
},
|
144
|
+
{
|
145
|
+
'name': 'Progress Bar Test',
|
146
|
+
'description': 'Test payment progress bars with different percentages',
|
147
|
+
'endpoint': '/payments/test/?test=progress_bars',
|
148
|
+
'available': True,
|
149
|
+
},
|
150
|
+
{
|
151
|
+
'name': 'Provider Statistics Test',
|
152
|
+
'description': 'Test provider statistics with sample data',
|
153
|
+
'endpoint': '/payments/test/?test=provider_stats',
|
154
|
+
'available': True,
|
155
|
+
},
|
156
|
+
{
|
157
|
+
'name': 'Real-time Updates Test',
|
158
|
+
'description': 'Test WebSocket connections and real-time updates',
|
159
|
+
'endpoint': '/payments/test/?test=realtime',
|
160
|
+
'available': False, # Requires WebSocket setup
|
161
|
+
},
|
162
|
+
{
|
163
|
+
'name': 'QR Code Generation Test',
|
164
|
+
'description': 'Test QR code generation for crypto payments',
|
165
|
+
'endpoint': '/payments/test/?test=qr_codes',
|
166
|
+
'available': True,
|
167
|
+
},
|
168
|
+
{
|
169
|
+
'name': 'API Integration Test',
|
170
|
+
'description': 'Test payment provider API integrations',
|
171
|
+
'endpoint': '/payments/test/?test=api_integration',
|
172
|
+
'available': False, # Requires API keys
|
173
|
+
},
|
174
|
+
]
|
175
|
+
|
176
|
+
# Add current test parameter
|
177
|
+
current_test = self.request.GET.get('test', 'overview')
|
178
|
+
for scenario in scenarios:
|
179
|
+
scenario['is_current'] = scenario['endpoint'].endswith(f'test={current_test}')
|
180
|
+
|
181
|
+
return scenarios
|
@@ -3,7 +3,7 @@ Webhook processing views with signature validation.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import json
|
6
|
-
import
|
6
|
+
from django_cfg.modules.django_logger import get_logger
|
7
7
|
from typing import Dict, Any
|
8
8
|
|
9
9
|
from django.http import JsonResponse, HttpResponse
|
@@ -20,7 +20,7 @@ from ..tasks.webhook_processing import process_webhook_with_fallback
|
|
20
20
|
from ..services.security.webhook_validator import webhook_validator
|
21
21
|
from ..services.security.error_handler import error_handler, SecurityError, ValidationError
|
22
22
|
|
23
|
-
logger =
|
23
|
+
logger = get_logger("webhook_views")
|
24
24
|
|
25
25
|
|
26
26
|
@csrf_exempt
|
@@ -19,7 +19,7 @@ from .views import (
|
|
19
19
|
UserAPIKeyViewSet, APIKeyViewSet,
|
20
20
|
|
21
21
|
# Currency ViewSets
|
22
|
-
CurrencyViewSet,
|
22
|
+
CurrencyViewSet, NetworkViewSet, ProviderCurrencyViewSet,
|
23
23
|
|
24
24
|
# Tariff ViewSets
|
25
25
|
TariffViewSet, TariffEndpointGroupViewSet,
|
@@ -49,7 +49,8 @@ class PaymentSystemRouter:
|
|
49
49
|
|
50
50
|
# Currency and pricing
|
51
51
|
self.router.register(r'currencies', CurrencyViewSet, basename='currency')
|
52
|
-
self.router.register(r'
|
52
|
+
self.router.register(r'networks', NetworkViewSet, basename='network')
|
53
|
+
self.router.register(r'provider-currencies', ProviderCurrencyViewSet, basename='provider-currency')
|
53
54
|
self.router.register(r'tariffs', TariffViewSet, basename='tariff')
|
54
55
|
self.router.register(r'tariff-groups', TariffEndpointGroupViewSet, basename='tariff-group')
|
55
56
|
|
django_cfg/apps/tasks/urls.py
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
URLs for Django CFG Tasks app.
|
3
|
+
|
4
|
+
Provides RESTful endpoints for task queue management and monitoring using ViewSets and routers.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.urls import path
|
8
|
+
from . import views
|
9
|
+
|
10
|
+
urlpatterns = [
|
11
|
+
|
12
|
+
# Dashboard view
|
13
|
+
path('dashboard/', views.dashboard_view, name='dashboard'),
|
14
|
+
]
|
django_cfg/apps/urls.py
CHANGED
@@ -46,11 +46,14 @@ def get_django_cfg_urlpatterns() -> List[URLPattern]:
|
|
46
46
|
|
47
47
|
# Tasks app - enabled when knowbase or agents are enabled
|
48
48
|
if base_module.should_enable_tasks():
|
49
|
-
patterns.append(path('
|
49
|
+
patterns.append(path('admin/django_cfg_tasks/admin/', include('django_cfg.apps.tasks.urls_admin')))
|
50
50
|
|
51
51
|
# Maintenance app - multi-site maintenance mode with Cloudflare
|
52
|
-
if base_module.is_maintenance_enabled():
|
53
|
-
|
52
|
+
# if base_module.is_maintenance_enabled():
|
53
|
+
# patterns.append(path('admin/django_cfg_maintenance/', include('django_cfg.apps.maintenance.urls_admin')))
|
54
|
+
|
55
|
+
if base_module.is_payments_enabled():
|
56
|
+
patterns.append(path('admin/django_cfg_payments/admin/', include('django_cfg.apps.payments.urls_admin')))
|
54
57
|
|
55
58
|
except Exception:
|
56
59
|
# Fallback: include all URLs if config is not available
|