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,212 @@
|
|
1
|
+
"""
|
2
|
+
Base mixins and decorators for payment template views.
|
3
|
+
|
4
|
+
Provides security and common functionality for all payment dashboard views.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib.auth.decorators import user_passes_test
|
8
|
+
from django.utils.decorators import method_decorator
|
9
|
+
from django.db.models import Q, Count, Sum
|
10
|
+
from django.utils import timezone
|
11
|
+
from datetime import timedelta
|
12
|
+
from django_cfg.modules.django_logger import get_logger
|
13
|
+
|
14
|
+
logger = get_logger("view_base")
|
15
|
+
|
16
|
+
|
17
|
+
def superuser_required(function=None):
|
18
|
+
"""Decorator that checks if user is superuser."""
|
19
|
+
actual_decorator = user_passes_test(
|
20
|
+
lambda u: u.is_authenticated and u.is_superuser,
|
21
|
+
login_url='/admin/login/'
|
22
|
+
)
|
23
|
+
if function:
|
24
|
+
return actual_decorator(function)
|
25
|
+
return actual_decorator
|
26
|
+
|
27
|
+
|
28
|
+
class SuperuserRequiredMixin:
|
29
|
+
"""Mixin that requires superuser access for all views."""
|
30
|
+
|
31
|
+
@method_decorator(superuser_required)
|
32
|
+
def dispatch(self, request, *args, **kwargs):
|
33
|
+
return super().dispatch(request, *args, **kwargs)
|
34
|
+
|
35
|
+
|
36
|
+
class PaymentFilterMixin:
|
37
|
+
"""Mixin that provides common payment filtering functionality."""
|
38
|
+
|
39
|
+
def get_filtered_payments(self, request=None):
|
40
|
+
"""Get payments with common filters applied."""
|
41
|
+
if request is None:
|
42
|
+
request = self.request
|
43
|
+
|
44
|
+
from ...models import UniversalPayment
|
45
|
+
|
46
|
+
# Base queryset
|
47
|
+
payments_qs = UniversalPayment.objects.select_related('user')
|
48
|
+
|
49
|
+
# Apply filters from GET parameters
|
50
|
+
status_filter = request.GET.get('status', '')
|
51
|
+
provider_filter = request.GET.get('provider', '')
|
52
|
+
search_query = request.GET.get('search', '')
|
53
|
+
date_filter = request.GET.get('date', '')
|
54
|
+
|
55
|
+
if status_filter:
|
56
|
+
payments_qs = payments_qs.filter(status=status_filter)
|
57
|
+
if provider_filter:
|
58
|
+
payments_qs = payments_qs.filter(provider=provider_filter)
|
59
|
+
if search_query:
|
60
|
+
payments_qs = payments_qs.filter(
|
61
|
+
Q(internal_payment_id__icontains=search_query) |
|
62
|
+
Q(provider_payment_id__icontains=search_query) |
|
63
|
+
Q(amount_usd__icontains=search_query) |
|
64
|
+
Q(user__email__icontains=search_query)
|
65
|
+
)
|
66
|
+
if date_filter:
|
67
|
+
try:
|
68
|
+
filter_date = timezone.datetime.strptime(date_filter, '%Y-%m-%d').date()
|
69
|
+
payments_qs = payments_qs.filter(created_at__date=filter_date)
|
70
|
+
except ValueError:
|
71
|
+
pass # Invalid date format, ignore filter
|
72
|
+
|
73
|
+
return payments_qs
|
74
|
+
|
75
|
+
def get_filter_context(self, request=None):
|
76
|
+
"""Get filter values for template context."""
|
77
|
+
if request is None:
|
78
|
+
request = self.request
|
79
|
+
|
80
|
+
return {
|
81
|
+
'status': request.GET.get('status', ''),
|
82
|
+
'provider': request.GET.get('provider', ''),
|
83
|
+
'search': request.GET.get('search', ''),
|
84
|
+
'date': request.GET.get('date', ''),
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
class PaymentStatsMixin:
|
89
|
+
"""Mixin that provides payment statistics functionality."""
|
90
|
+
|
91
|
+
def get_payment_stats(self, queryset=None):
|
92
|
+
"""Get payment statistics from queryset or all payments."""
|
93
|
+
if queryset is None:
|
94
|
+
from ...models import UniversalPayment
|
95
|
+
queryset = UniversalPayment.objects.all()
|
96
|
+
|
97
|
+
stats = queryset.aggregate(
|
98
|
+
total_count=Count('id'),
|
99
|
+
pending_count=Count('id', filter=Q(status='pending')),
|
100
|
+
confirming_count=Count('id', filter=Q(status='confirming')),
|
101
|
+
completed_count=Count('id', filter=Q(status='completed')),
|
102
|
+
failed_count=Count('id', filter=Q(status='failed')),
|
103
|
+
total_volume=Sum('amount_usd')
|
104
|
+
)
|
105
|
+
|
106
|
+
# Convert to template format
|
107
|
+
return {
|
108
|
+
'total_payments_count': stats['total_count'] or 0,
|
109
|
+
'pending_payments_count': stats['pending_count'] or 0,
|
110
|
+
'confirming_payments_count': stats['confirming_count'] or 0,
|
111
|
+
'completed_payments_count': stats['completed_count'] or 0,
|
112
|
+
'failed_payments_count': stats['failed_count'] or 0,
|
113
|
+
'total_volume': float(stats['total_volume'] or 0),
|
114
|
+
}
|
115
|
+
|
116
|
+
def get_provider_stats(self, queryset=None):
|
117
|
+
"""Get provider-specific statistics."""
|
118
|
+
if queryset is None:
|
119
|
+
from ...models import UniversalPayment
|
120
|
+
queryset = UniversalPayment.objects.all()
|
121
|
+
|
122
|
+
provider_stats = queryset.values('provider').annotate(
|
123
|
+
count=Count('id'),
|
124
|
+
volume=Sum('amount_usd'),
|
125
|
+
completed_count=Count('id', filter=Q(status='completed')),
|
126
|
+
).order_by('-volume')
|
127
|
+
|
128
|
+
# Calculate success rate and convert to list of dicts
|
129
|
+
stats_list = []
|
130
|
+
for stat in provider_stats:
|
131
|
+
if stat['count'] > 0:
|
132
|
+
stat['success_rate'] = (stat['completed_count'] / stat['count']) * 100
|
133
|
+
else:
|
134
|
+
stat['success_rate'] = 0
|
135
|
+
|
136
|
+
# Convert Decimal to float
|
137
|
+
if stat['volume']:
|
138
|
+
stat['volume'] = float(stat['volume'])
|
139
|
+
else:
|
140
|
+
stat['volume'] = 0.0
|
141
|
+
|
142
|
+
stats_list.append(stat)
|
143
|
+
|
144
|
+
return stats_list
|
145
|
+
|
146
|
+
def get_time_range_stats(self, days=30):
|
147
|
+
"""Get statistics for a specific time range."""
|
148
|
+
from ...models import UniversalPayment
|
149
|
+
|
150
|
+
end_date = timezone.now()
|
151
|
+
start_date = end_date - timedelta(days=days)
|
152
|
+
|
153
|
+
queryset = UniversalPayment.objects.filter(
|
154
|
+
created_at__gte=start_date,
|
155
|
+
created_at__lte=end_date
|
156
|
+
)
|
157
|
+
|
158
|
+
return self.get_payment_stats(queryset)
|
159
|
+
|
160
|
+
|
161
|
+
class PaymentContextMixin:
|
162
|
+
"""Mixin that provides common context data for payment views."""
|
163
|
+
|
164
|
+
def get_common_context(self):
|
165
|
+
"""Get common context data used across multiple views."""
|
166
|
+
from ...models import PaymentEvent
|
167
|
+
|
168
|
+
# Get recent events for activity feed (if any exist)
|
169
|
+
try:
|
170
|
+
recent_events = PaymentEvent.objects.order_by('-created_at')[:10]
|
171
|
+
except Exception:
|
172
|
+
recent_events = []
|
173
|
+
|
174
|
+
return {
|
175
|
+
'recent_events': recent_events,
|
176
|
+
'page_title': self.get_page_title(),
|
177
|
+
'breadcrumbs': self.get_breadcrumbs(),
|
178
|
+
}
|
179
|
+
|
180
|
+
def get_page_title(self):
|
181
|
+
"""Get page title for the view."""
|
182
|
+
return getattr(self, 'page_title', 'Payment Dashboard')
|
183
|
+
|
184
|
+
def get_breadcrumbs(self):
|
185
|
+
"""Get breadcrumb navigation for the view."""
|
186
|
+
return getattr(self, 'breadcrumbs', [
|
187
|
+
{'name': 'Payment Dashboard', 'url': '/payments/admin/'},
|
188
|
+
])
|
189
|
+
|
190
|
+
|
191
|
+
def get_progress_percentage(status):
|
192
|
+
"""Helper function to calculate progress percentage."""
|
193
|
+
progress_map = {
|
194
|
+
'pending': 10,
|
195
|
+
'confirming': 40,
|
196
|
+
'confirmed': 70,
|
197
|
+
'completed': 100,
|
198
|
+
'failed': 0,
|
199
|
+
'expired': 0,
|
200
|
+
'cancelled': 0,
|
201
|
+
'refunded': 50,
|
202
|
+
}
|
203
|
+
return progress_map.get(status, 0)
|
204
|
+
|
205
|
+
|
206
|
+
def log_view_access(view_name, user, **kwargs):
|
207
|
+
"""Log access to payment views for audit purposes."""
|
208
|
+
extra_info = ', '.join([f"{k}={v}" for k, v in kwargs.items()])
|
209
|
+
logger.info(
|
210
|
+
f"Payment dashboard access: {view_name} by {user.email} "
|
211
|
+
f"(superuser={user.is_superuser}) {extra_info}"
|
212
|
+
)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"""
|
2
|
+
Main payment dashboard view.
|
3
|
+
|
4
|
+
Provides overview, statistics, and recent payments for superuser access.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.views.generic import TemplateView
|
8
|
+
from .base import (
|
9
|
+
SuperuserRequiredMixin,
|
10
|
+
PaymentFilterMixin,
|
11
|
+
PaymentStatsMixin,
|
12
|
+
PaymentContextMixin,
|
13
|
+
log_view_access
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class PaymentDashboardView(
|
18
|
+
SuperuserRequiredMixin,
|
19
|
+
PaymentFilterMixin,
|
20
|
+
PaymentStatsMixin,
|
21
|
+
PaymentContextMixin,
|
22
|
+
TemplateView
|
23
|
+
):
|
24
|
+
"""Main payment dashboard with overview and recent payments."""
|
25
|
+
|
26
|
+
template_name = 'payments/dashboard.html'
|
27
|
+
page_title = 'Payment Dashboard'
|
28
|
+
|
29
|
+
def get_context_data(self, **kwargs):
|
30
|
+
context = super().get_context_data(**kwargs)
|
31
|
+
|
32
|
+
# Log access for audit
|
33
|
+
log_view_access('dashboard', self.request.user)
|
34
|
+
|
35
|
+
# Get filtered payments
|
36
|
+
payments_qs = self.get_filtered_payments()
|
37
|
+
|
38
|
+
# Get recent payments (limit to 20 for performance)
|
39
|
+
recent_payments = payments_qs.order_by('-created_at')[:20]
|
40
|
+
|
41
|
+
# Check if there are more payments for pagination
|
42
|
+
has_more = payments_qs.count() > 20
|
43
|
+
|
44
|
+
# Get statistics
|
45
|
+
payment_stats = self.get_payment_stats()
|
46
|
+
provider_stats = self.get_provider_stats()
|
47
|
+
|
48
|
+
# Get common context
|
49
|
+
common_context = self.get_common_context()
|
50
|
+
|
51
|
+
context.update({
|
52
|
+
'payments': recent_payments,
|
53
|
+
'has_more_payments': has_more,
|
54
|
+
'payment_stats': payment_stats,
|
55
|
+
'provider_stats': provider_stats,
|
56
|
+
'filters': self.get_filter_context(),
|
57
|
+
**common_context
|
58
|
+
})
|
59
|
+
|
60
|
+
return context
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""
|
2
|
+
Payment detail view.
|
3
|
+
|
4
|
+
Provides detailed information about a single payment for superuser access.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.views.generic import DetailView
|
8
|
+
from .base import (
|
9
|
+
SuperuserRequiredMixin,
|
10
|
+
PaymentContextMixin,
|
11
|
+
log_view_access
|
12
|
+
)
|
13
|
+
from ...models import UniversalPayment, PaymentEvent
|
14
|
+
|
15
|
+
|
16
|
+
class PaymentDetailView(
|
17
|
+
SuperuserRequiredMixin,
|
18
|
+
PaymentContextMixin,
|
19
|
+
DetailView
|
20
|
+
):
|
21
|
+
"""Detailed view for a single payment."""
|
22
|
+
|
23
|
+
model = UniversalPayment
|
24
|
+
template_name = 'payments/payment_detail.html'
|
25
|
+
context_object_name = 'payment'
|
26
|
+
page_title = 'Payment Details'
|
27
|
+
|
28
|
+
def get_breadcrumbs(self):
|
29
|
+
payment = self.get_object()
|
30
|
+
return [
|
31
|
+
{'name': 'Dashboard', 'url': '/payments/admin/'},
|
32
|
+
{'name': 'Payments', 'url': '/payments/admin/list/'},
|
33
|
+
{'name': f'Payment #{payment.internal_payment_id or str(payment.id)[:8]}', 'url': ''},
|
34
|
+
]
|
35
|
+
|
36
|
+
def get_context_data(self, **kwargs):
|
37
|
+
context = super().get_context_data(**kwargs)
|
38
|
+
payment = self.get_object()
|
39
|
+
|
40
|
+
# Log access for audit
|
41
|
+
log_view_access('payment_detail', self.request.user, payment_id=payment.id)
|
42
|
+
|
43
|
+
# Get payment events for this payment
|
44
|
+
events = PaymentEvent.objects.filter(payment_id=payment.id).order_by('-created_at')
|
45
|
+
|
46
|
+
# Get related payments (same user, similar amount range)
|
47
|
+
related_payments = UniversalPayment.objects.filter(
|
48
|
+
user=payment.user,
|
49
|
+
amount_usd__gte=payment.amount_usd * 0.8,
|
50
|
+
amount_usd__lte=payment.amount_usd * 1.2
|
51
|
+
).exclude(id=payment.id).order_by('-created_at')[:5]
|
52
|
+
|
53
|
+
# Get provider-specific information
|
54
|
+
provider_info = self._get_provider_info(payment)
|
55
|
+
|
56
|
+
# Get common context
|
57
|
+
common_context = self.get_common_context()
|
58
|
+
|
59
|
+
context.update({
|
60
|
+
'events': events,
|
61
|
+
'related_payments': related_payments,
|
62
|
+
'provider_info': provider_info,
|
63
|
+
'can_retry': self._can_retry_payment(payment),
|
64
|
+
'can_cancel': self._can_cancel_payment(payment),
|
65
|
+
'can_refund': self._can_refund_payment(payment),
|
66
|
+
**common_context
|
67
|
+
})
|
68
|
+
|
69
|
+
return context
|
70
|
+
|
71
|
+
def _get_provider_info(self, payment):
|
72
|
+
"""Get provider-specific information for the payment."""
|
73
|
+
info = {
|
74
|
+
'display_name': payment.provider.title(),
|
75
|
+
'is_crypto': payment.provider in ['nowpayments', 'cryptapi', 'cryptomus'],
|
76
|
+
'supports_qr': payment.provider in ['nowpayments', 'cryptapi', 'cryptomus'],
|
77
|
+
'supports_webhook': True,
|
78
|
+
}
|
79
|
+
|
80
|
+
# Add crypto-specific info
|
81
|
+
if info['is_crypto'] and payment.pay_address:
|
82
|
+
info.update({
|
83
|
+
'crypto_address': payment.pay_address,
|
84
|
+
'crypto_amount': payment.pay_amount,
|
85
|
+
'crypto_currency': payment.currency_code,
|
86
|
+
'network': getattr(payment, 'network', 'mainnet'),
|
87
|
+
'confirmations': getattr(payment, 'confirmations_count', 0),
|
88
|
+
})
|
89
|
+
|
90
|
+
return info
|
91
|
+
|
92
|
+
def _can_retry_payment(self, payment):
|
93
|
+
"""Check if payment can be retried."""
|
94
|
+
return payment.status in ['failed', 'expired']
|
95
|
+
|
96
|
+
def _can_cancel_payment(self, payment):
|
97
|
+
"""Check if payment can be cancelled."""
|
98
|
+
return payment.status in ['pending', 'confirming']
|
99
|
+
|
100
|
+
def _can_refund_payment(self, payment):
|
101
|
+
"""Check if payment can be refunded."""
|
102
|
+
return payment.status == 'completed'
|
@@ -0,0 +1,158 @@
|
|
1
|
+
"""
|
2
|
+
Payment management views.
|
3
|
+
|
4
|
+
Provides list, create, and management functionality for payments.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
from django.views.generic import TemplateView, ListView
|
9
|
+
from .base import (
|
10
|
+
SuperuserRequiredMixin,
|
11
|
+
PaymentFilterMixin,
|
12
|
+
PaymentContextMixin,
|
13
|
+
log_view_access
|
14
|
+
)
|
15
|
+
from ...models import UniversalPayment, Currency, PaymentProvider
|
16
|
+
from ...services.providers.registry import ProviderRegistry
|
17
|
+
|
18
|
+
|
19
|
+
class PaymentCreateView(
|
20
|
+
SuperuserRequiredMixin,
|
21
|
+
PaymentContextMixin,
|
22
|
+
TemplateView
|
23
|
+
):
|
24
|
+
"""Form view for creating a new payment."""
|
25
|
+
|
26
|
+
template_name = 'payments/payment_create.html'
|
27
|
+
page_title = 'Create Payment'
|
28
|
+
|
29
|
+
def get_breadcrumbs(self):
|
30
|
+
return [
|
31
|
+
{'name': 'Dashboard', 'url': '/payments/admin/'},
|
32
|
+
{'name': 'Payments', 'url': '/payments/admin/list/'},
|
33
|
+
{'name': 'Create Payment', '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_create', self.request.user)
|
41
|
+
|
42
|
+
# Get available providers
|
43
|
+
providers = self._get_available_providers()
|
44
|
+
|
45
|
+
# Get available currencies
|
46
|
+
currencies = self._get_available_currencies()
|
47
|
+
|
48
|
+
# Get common context
|
49
|
+
common_context = self.get_common_context()
|
50
|
+
|
51
|
+
# Provider enum with defaults (serialize to JSON for JavaScript)
|
52
|
+
provider_defaults = {
|
53
|
+
PaymentProvider.NOWPAYMENTS.value: 'USDTTRC20',
|
54
|
+
PaymentProvider.CRYPTAPI.value: 'BTC',
|
55
|
+
PaymentProvider.CRYPTOMUS.value: 'USDTTRC20',
|
56
|
+
PaymentProvider.STRIPE.value: 'USD'
|
57
|
+
}
|
58
|
+
provider_defaults_json = json.dumps(provider_defaults)
|
59
|
+
|
60
|
+
context.update({
|
61
|
+
'providers': providers,
|
62
|
+
'currencies': currencies,
|
63
|
+
'provider_enum': PaymentProvider,
|
64
|
+
'provider_defaults_json': provider_defaults_json,
|
65
|
+
'default_amount': 10.0, # Default test amount
|
66
|
+
**common_context
|
67
|
+
})
|
68
|
+
|
69
|
+
return context
|
70
|
+
|
71
|
+
def _get_available_providers(self):
|
72
|
+
"""Get list of available payment providers from registry."""
|
73
|
+
registry = ProviderRegistry()
|
74
|
+
provider_names = registry.list_providers()
|
75
|
+
|
76
|
+
providers = []
|
77
|
+
for provider_name in provider_names:
|
78
|
+
provider_instance = registry.get_provider(provider_name)
|
79
|
+
providers.append({
|
80
|
+
'name': provider_name,
|
81
|
+
'display_name': provider_name.title(),
|
82
|
+
'is_crypto': provider_name in ['nowpayments', 'cryptapi', 'cryptomus'],
|
83
|
+
'description': getattr(provider_instance.__class__, '__doc__', '') if provider_instance else '',
|
84
|
+
})
|
85
|
+
return providers
|
86
|
+
|
87
|
+
def _get_available_currencies(self):
|
88
|
+
"""Get list of available currencies from database."""
|
89
|
+
# Get all active currencies - both fiat and crypto
|
90
|
+
return Currency.objects.all().order_by('currency_type', 'code')
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
class PaymentListView(
|
96
|
+
SuperuserRequiredMixin,
|
97
|
+
PaymentFilterMixin,
|
98
|
+
PaymentContextMixin,
|
99
|
+
ListView
|
100
|
+
):
|
101
|
+
"""Paginated list view for all payments."""
|
102
|
+
|
103
|
+
model = UniversalPayment
|
104
|
+
template_name = 'payments/payment_list.html'
|
105
|
+
context_object_name = 'payments'
|
106
|
+
paginate_by = 20
|
107
|
+
ordering = ['-created_at']
|
108
|
+
page_title = 'All Payments'
|
109
|
+
|
110
|
+
def get_breadcrumbs(self):
|
111
|
+
return [
|
112
|
+
{'name': 'Dashboard', 'url': '/payments/admin/'},
|
113
|
+
{'name': 'All Payments', 'url': ''},
|
114
|
+
]
|
115
|
+
|
116
|
+
def get_queryset(self):
|
117
|
+
# Log access for audit
|
118
|
+
log_view_access('payment_list', self.request.user)
|
119
|
+
|
120
|
+
# Use filter mixin to get filtered queryset
|
121
|
+
return self.get_filtered_payments().order_by(*self.ordering)
|
122
|
+
|
123
|
+
def get_context_data(self, **kwargs):
|
124
|
+
context = super().get_context_data(**kwargs)
|
125
|
+
|
126
|
+
# Get filter context
|
127
|
+
filter_context = self.get_filter_context()
|
128
|
+
|
129
|
+
# Get available filter options
|
130
|
+
filter_options = self._get_filter_options()
|
131
|
+
|
132
|
+
# Get common context
|
133
|
+
common_context = self.get_common_context()
|
134
|
+
|
135
|
+
context.update({
|
136
|
+
'filters': filter_context,
|
137
|
+
'filter_options': filter_options,
|
138
|
+
'total_count': self.get_queryset().count(),
|
139
|
+
**common_context
|
140
|
+
})
|
141
|
+
|
142
|
+
return context
|
143
|
+
|
144
|
+
def _get_filter_options(self):
|
145
|
+
"""Get available options for filter dropdowns."""
|
146
|
+
|
147
|
+
# Get unique statuses
|
148
|
+
statuses = UniversalPayment.objects.values_list('status', flat=True).distinct()
|
149
|
+
status_choices = [(status, status.title()) for status in statuses if status]
|
150
|
+
|
151
|
+
# Get unique providers
|
152
|
+
providers = UniversalPayment.objects.values_list('provider', flat=True).distinct()
|
153
|
+
provider_choices = [(provider, provider.title()) for provider in providers if provider]
|
154
|
+
|
155
|
+
return {
|
156
|
+
'statuses': status_choices,
|
157
|
+
'providers': provider_choices,
|
158
|
+
}
|