django-cfg 1.2.29__py3-none-any.whl → 1.3.1__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/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -9
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +600 -108
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +470 -64
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
- django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +16 -20
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +207 -67
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -284
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +344 -468
- django_cfg/apps/payments/services/core/subscription_service.py +425 -484
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +232 -71
- django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
- django_cfg/apps/payments/services/providers/registry.py +429 -80
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +211 -130
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +129 -98
- django_cfg/apps/payments/signals/subscription_signals.py +195 -143
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +46 -47
- django_cfg/apps/payments/urls_admin.py +49 -0
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +4 -4
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +75 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -498
- 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_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +65 -12
- django_cfg/registry/core.py +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/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -178
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
- 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/managers/__init__.py +0 -22
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -83
- django_cfg/apps/payments/managers/payment_manager.py +0 -44
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -56
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -55
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -297
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -637
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/urls_templates.py +0 -52
- django_cfg/apps/payments/utils/__init__.py +0 -45
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -245
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -62
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -111
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -312
- django_cfg/apps/payments/views/templates/base.py +0 -204
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -164
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -240
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -65
- django_cfg/core/integration.py +0 -160
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,415 @@
|
|
1
|
+
"""
|
2
|
+
Payment ViewSets for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
DRF ViewSets with service layer integration and nested routing support.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from rest_framework import viewsets, permissions, status, generics
|
8
|
+
from rest_framework.decorators import action
|
9
|
+
from rest_framework.response import Response
|
10
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
11
|
+
from django.contrib.auth import get_user_model
|
12
|
+
from django.shortcuts import get_object_or_404
|
13
|
+
|
14
|
+
from .base import PaymentBaseViewSet, NestedPaymentViewSet
|
15
|
+
from ...models import UniversalPayment
|
16
|
+
from ...services import get_payment_service
|
17
|
+
from ..serializers.payments import (
|
18
|
+
PaymentSerializer,
|
19
|
+
PaymentCreateSerializer,
|
20
|
+
PaymentListSerializer,
|
21
|
+
PaymentStatusSerializer,
|
22
|
+
PaymentCancelSerializer,
|
23
|
+
PaymentStatsSerializer,
|
24
|
+
)
|
25
|
+
from django_cfg.modules.django_logger import get_logger
|
26
|
+
|
27
|
+
User = get_user_model()
|
28
|
+
logger = get_logger("payment_viewsets")
|
29
|
+
|
30
|
+
|
31
|
+
class PaymentViewSet(PaymentBaseViewSet):
|
32
|
+
"""
|
33
|
+
Global payment ViewSet: /api/v1/payments/
|
34
|
+
|
35
|
+
Provides admin-level access to all payments with filtering and stats.
|
36
|
+
"""
|
37
|
+
|
38
|
+
queryset = UniversalPayment.objects.all()
|
39
|
+
serializer_class = PaymentSerializer
|
40
|
+
permission_classes = [permissions.IsAdminUser] # Admin only for global access
|
41
|
+
filterset_fields = ['status', 'provider', 'currency_code', 'user']
|
42
|
+
search_fields = ['description', 'provider_payment_id', 'transaction_hash']
|
43
|
+
ordering_fields = ['created_at', 'updated_at', 'amount_usd', 'expires_at']
|
44
|
+
|
45
|
+
serializer_classes = {
|
46
|
+
'list': PaymentListSerializer,
|
47
|
+
'create': PaymentCreateSerializer,
|
48
|
+
'retrieve': PaymentSerializer,
|
49
|
+
'update': PaymentSerializer,
|
50
|
+
'partial_update': PaymentSerializer,
|
51
|
+
}
|
52
|
+
|
53
|
+
def get_queryset(self):
|
54
|
+
"""Optimized queryset with related objects."""
|
55
|
+
return super().get_queryset().select_related('user').prefetch_related(
|
56
|
+
'user__userbalance_set'
|
57
|
+
)
|
58
|
+
|
59
|
+
@action(detail=True, methods=['post'])
|
60
|
+
def check_status(self, request, pk=None):
|
61
|
+
"""
|
62
|
+
Check payment status with provider.
|
63
|
+
|
64
|
+
POST /api/v1/payments/{id}/check_status/
|
65
|
+
"""
|
66
|
+
payment = self.get_object()
|
67
|
+
|
68
|
+
serializer = PaymentStatusSerializer(
|
69
|
+
data=request.data,
|
70
|
+
context={
|
71
|
+
**self.get_serializer_context(),
|
72
|
+
'payment_id': str(payment.id)
|
73
|
+
}
|
74
|
+
)
|
75
|
+
|
76
|
+
if serializer.is_valid():
|
77
|
+
result = serializer.save()
|
78
|
+
return Response(result)
|
79
|
+
|
80
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
81
|
+
|
82
|
+
@action(detail=True, methods=['post'])
|
83
|
+
def cancel(self, request, pk=None):
|
84
|
+
"""
|
85
|
+
Cancel payment.
|
86
|
+
|
87
|
+
POST /api/v1/payments/{id}/cancel/
|
88
|
+
"""
|
89
|
+
payment = self.get_object()
|
90
|
+
|
91
|
+
if not payment.can_be_cancelled():
|
92
|
+
return Response(
|
93
|
+
{'error': f'Payment cannot be cancelled (status: {payment.status})'},
|
94
|
+
status=status.HTTP_400_BAD_REQUEST
|
95
|
+
)
|
96
|
+
|
97
|
+
serializer = PaymentCancelSerializer(
|
98
|
+
data=request.data,
|
99
|
+
context={
|
100
|
+
**self.get_serializer_context(),
|
101
|
+
'payment_id': str(payment.id)
|
102
|
+
}
|
103
|
+
)
|
104
|
+
|
105
|
+
if serializer.is_valid():
|
106
|
+
result = serializer.save()
|
107
|
+
return Response(result)
|
108
|
+
|
109
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
110
|
+
|
111
|
+
@action(detail=False, methods=['get'])
|
112
|
+
def analytics(self, request):
|
113
|
+
"""
|
114
|
+
Get payment analytics.
|
115
|
+
|
116
|
+
GET /api/v1/payments/analytics/?days=30
|
117
|
+
"""
|
118
|
+
serializer = PaymentStatsSerializer(data=request.query_params)
|
119
|
+
|
120
|
+
if serializer.is_valid():
|
121
|
+
result = serializer.save()
|
122
|
+
return Response(result)
|
123
|
+
|
124
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
125
|
+
|
126
|
+
@action(detail=False, methods=['get'])
|
127
|
+
def by_provider(self, request):
|
128
|
+
"""
|
129
|
+
Get payments grouped by provider.
|
130
|
+
|
131
|
+
GET /api/v1/payments/by_provider/
|
132
|
+
"""
|
133
|
+
try:
|
134
|
+
queryset = self.filter_queryset(self.get_queryset())
|
135
|
+
|
136
|
+
provider_stats = {}
|
137
|
+
for provider_choice in UniversalPayment.PaymentProvider.choices:
|
138
|
+
provider_code = provider_choice[0]
|
139
|
+
provider_name = provider_choice[1]
|
140
|
+
|
141
|
+
provider_payments = queryset.filter(provider=provider_code)
|
142
|
+
|
143
|
+
provider_stats[provider_code] = {
|
144
|
+
'name': provider_name,
|
145
|
+
'total_payments': provider_payments.count(),
|
146
|
+
'total_amount_usd': float(
|
147
|
+
provider_payments.aggregate(
|
148
|
+
total=models.Sum('amount_usd')
|
149
|
+
)['total'] or 0
|
150
|
+
),
|
151
|
+
'status_breakdown': dict(
|
152
|
+
provider_payments.values('status')
|
153
|
+
.annotate(count=models.Count('id'))
|
154
|
+
.values_list('status', 'count')
|
155
|
+
)
|
156
|
+
}
|
157
|
+
|
158
|
+
return Response({
|
159
|
+
'provider_stats': provider_stats,
|
160
|
+
'generated_at': timezone.now().isoformat()
|
161
|
+
})
|
162
|
+
|
163
|
+
except Exception as e:
|
164
|
+
logger.error(f"Provider stats failed: {e}")
|
165
|
+
return Response(
|
166
|
+
{'error': f'Provider stats failed: {e}'},
|
167
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
168
|
+
)
|
169
|
+
|
170
|
+
|
171
|
+
class UserPaymentViewSet(NestedPaymentViewSet):
|
172
|
+
"""
|
173
|
+
User-specific payment ViewSet: /api/v1/users/{user_id}/payments/
|
174
|
+
|
175
|
+
Provides user-scoped access to payments with full CRUD operations.
|
176
|
+
"""
|
177
|
+
|
178
|
+
queryset = UniversalPayment.objects.all()
|
179
|
+
serializer_class = PaymentSerializer
|
180
|
+
permission_classes = [permissions.IsAuthenticated]
|
181
|
+
filterset_fields = ['status', 'provider', 'currency_code']
|
182
|
+
search_fields = ['description', 'provider_payment_id']
|
183
|
+
ordering_fields = ['created_at', 'updated_at', 'amount_usd', 'expires_at']
|
184
|
+
|
185
|
+
# Nested ViewSet configuration
|
186
|
+
parent_lookup_field = 'user_pk'
|
187
|
+
parent_model_field = 'user'
|
188
|
+
|
189
|
+
serializer_classes = {
|
190
|
+
'list': PaymentListSerializer,
|
191
|
+
'create': PaymentCreateSerializer,
|
192
|
+
'retrieve': PaymentSerializer,
|
193
|
+
}
|
194
|
+
|
195
|
+
def get_queryset(self):
|
196
|
+
"""Filter by user and optimize queryset."""
|
197
|
+
queryset = super().get_queryset()
|
198
|
+
|
199
|
+
# Additional permission check: users can only see their own payments
|
200
|
+
if not self.request.user.is_staff:
|
201
|
+
user_id = self.kwargs.get('user_pk')
|
202
|
+
if str(self.request.user.id) != str(user_id):
|
203
|
+
return queryset.none() # Return empty queryset for unauthorized access
|
204
|
+
|
205
|
+
return queryset
|
206
|
+
|
207
|
+
@action(detail=True, methods=['post'])
|
208
|
+
def check_status(self, request, user_pk=None, pk=None):
|
209
|
+
"""
|
210
|
+
Check payment status with provider.
|
211
|
+
|
212
|
+
POST /api/v1/users/{user_id}/payments/{id}/check_status/
|
213
|
+
"""
|
214
|
+
payment = self.get_object()
|
215
|
+
|
216
|
+
serializer = PaymentStatusSerializer(
|
217
|
+
data=request.data,
|
218
|
+
context={
|
219
|
+
**self.get_serializer_context(),
|
220
|
+
'payment_id': str(payment.id)
|
221
|
+
}
|
222
|
+
)
|
223
|
+
|
224
|
+
if serializer.is_valid():
|
225
|
+
result = serializer.save()
|
226
|
+
return Response(result)
|
227
|
+
|
228
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
229
|
+
|
230
|
+
@action(detail=True, methods=['post'])
|
231
|
+
def cancel(self, request, user_pk=None, pk=None):
|
232
|
+
"""
|
233
|
+
Cancel payment.
|
234
|
+
|
235
|
+
POST /api/v1/users/{user_id}/payments/{id}/cancel/
|
236
|
+
"""
|
237
|
+
payment = self.get_object()
|
238
|
+
|
239
|
+
if not payment.can_be_cancelled():
|
240
|
+
return Response(
|
241
|
+
{'error': f'Payment cannot be cancelled (status: {payment.status})'},
|
242
|
+
status=status.HTTP_400_BAD_REQUEST
|
243
|
+
)
|
244
|
+
|
245
|
+
serializer = PaymentCancelSerializer(
|
246
|
+
data=request.data,
|
247
|
+
context={
|
248
|
+
**self.get_serializer_context(),
|
249
|
+
'payment_id': str(payment.id)
|
250
|
+
}
|
251
|
+
)
|
252
|
+
|
253
|
+
if serializer.is_valid():
|
254
|
+
result = serializer.save()
|
255
|
+
return Response(result)
|
256
|
+
|
257
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
258
|
+
|
259
|
+
@action(detail=False, methods=['get'])
|
260
|
+
def summary(self, request, user_pk=None):
|
261
|
+
"""
|
262
|
+
Get user payment summary.
|
263
|
+
|
264
|
+
GET /api/v1/users/{user_id}/payments/summary/
|
265
|
+
"""
|
266
|
+
try:
|
267
|
+
queryset = self.filter_queryset(self.get_queryset())
|
268
|
+
|
269
|
+
from django.db import models
|
270
|
+
|
271
|
+
summary = queryset.aggregate(
|
272
|
+
total_payments=models.Count('id'),
|
273
|
+
total_amount_usd=models.Sum('amount_usd'),
|
274
|
+
completed_payments=models.Count(
|
275
|
+
'id',
|
276
|
+
filter=models.Q(status=UniversalPayment.PaymentStatus.COMPLETED)
|
277
|
+
),
|
278
|
+
pending_payments=models.Count(
|
279
|
+
'id',
|
280
|
+
filter=models.Q(status=UniversalPayment.PaymentStatus.PENDING)
|
281
|
+
),
|
282
|
+
failed_payments=models.Count(
|
283
|
+
'id',
|
284
|
+
filter=models.Q(status=UniversalPayment.PaymentStatus.FAILED)
|
285
|
+
),
|
286
|
+
)
|
287
|
+
|
288
|
+
# Calculate success rate
|
289
|
+
total = summary['total_payments']
|
290
|
+
completed = summary['completed_payments']
|
291
|
+
success_rate = (completed / total * 100) if total > 0 else 0
|
292
|
+
|
293
|
+
return Response({
|
294
|
+
'user_id': user_pk,
|
295
|
+
'summary': {
|
296
|
+
**summary,
|
297
|
+
'total_amount_usd': float(summary['total_amount_usd'] or 0),
|
298
|
+
'success_rate': round(success_rate, 2),
|
299
|
+
},
|
300
|
+
'generated_at': timezone.now().isoformat()
|
301
|
+
})
|
302
|
+
|
303
|
+
except Exception as e:
|
304
|
+
logger.error(f"User payment summary failed: {e}")
|
305
|
+
return Response(
|
306
|
+
{'error': f'Summary generation failed: {e}'},
|
307
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
308
|
+
)
|
309
|
+
|
310
|
+
|
311
|
+
class PaymentCreateView(generics.CreateAPIView):
|
312
|
+
"""
|
313
|
+
Standalone payment creation endpoint: /api/v1/payments/create/
|
314
|
+
|
315
|
+
Simplified endpoint for payment creation without full ViewSet overhead.
|
316
|
+
"""
|
317
|
+
|
318
|
+
serializer_class = PaymentCreateSerializer
|
319
|
+
permission_classes = [permissions.IsAuthenticated]
|
320
|
+
|
321
|
+
def create(self, request, *args, **kwargs):
|
322
|
+
"""Create payment with enhanced response."""
|
323
|
+
serializer = self.get_serializer(data=request.data)
|
324
|
+
|
325
|
+
if serializer.is_valid():
|
326
|
+
try:
|
327
|
+
payment = serializer.save()
|
328
|
+
|
329
|
+
response_data = {
|
330
|
+
'success': True,
|
331
|
+
'message': 'Payment created successfully',
|
332
|
+
'payment': PaymentSerializer(payment, context={'request': request}).data
|
333
|
+
}
|
334
|
+
|
335
|
+
return Response(response_data, status=status.HTTP_201_CREATED)
|
336
|
+
|
337
|
+
except Exception as e:
|
338
|
+
logger.error(f"Payment creation failed: {e}")
|
339
|
+
return Response(
|
340
|
+
{
|
341
|
+
'success': False,
|
342
|
+
'error': f'Payment creation failed: {e}',
|
343
|
+
'error_code': 'creation_failed'
|
344
|
+
},
|
345
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
346
|
+
)
|
347
|
+
|
348
|
+
return Response(
|
349
|
+
{
|
350
|
+
'success': False,
|
351
|
+
'error': 'Invalid payment data',
|
352
|
+
'errors': serializer.errors
|
353
|
+
},
|
354
|
+
status=status.HTTP_400_BAD_REQUEST
|
355
|
+
)
|
356
|
+
|
357
|
+
|
358
|
+
class PaymentStatusView(generics.RetrieveAPIView):
|
359
|
+
"""
|
360
|
+
Standalone payment status endpoint: /api/v1/payments/{id}/status/
|
361
|
+
|
362
|
+
Quick status check without full ViewSet overhead.
|
363
|
+
"""
|
364
|
+
|
365
|
+
queryset = UniversalPayment.objects.all()
|
366
|
+
serializer_class = PaymentSerializer
|
367
|
+
permission_classes = [permissions.IsAuthenticated]
|
368
|
+
lookup_field = 'id'
|
369
|
+
|
370
|
+
def get_object(self):
|
371
|
+
"""Get payment with permission check."""
|
372
|
+
payment = super().get_object()
|
373
|
+
|
374
|
+
# Users can only check their own payments unless they're staff
|
375
|
+
if not self.request.user.is_staff and payment.user != self.request.user:
|
376
|
+
from rest_framework.exceptions import PermissionDenied
|
377
|
+
raise PermissionDenied("You can only check your own payments")
|
378
|
+
|
379
|
+
return payment
|
380
|
+
|
381
|
+
def retrieve(self, request, *args, **kwargs):
|
382
|
+
"""Get payment status with optional provider check."""
|
383
|
+
payment = self.get_object()
|
384
|
+
|
385
|
+
# Check if force provider check is requested
|
386
|
+
force_check = request.query_params.get('force_check', 'false').lower() == 'true'
|
387
|
+
|
388
|
+
if force_check:
|
389
|
+
# Use PaymentService to check with provider
|
390
|
+
try:
|
391
|
+
payment_service = get_payment_service()
|
392
|
+
from ...services.types import PaymentStatusRequest
|
393
|
+
|
394
|
+
status_request = PaymentStatusRequest(
|
395
|
+
payment_id=str(payment.id),
|
396
|
+
user_id=request.user.id,
|
397
|
+
force_provider_check=True
|
398
|
+
)
|
399
|
+
|
400
|
+
result = payment_service.get_payment_status(status_request)
|
401
|
+
|
402
|
+
if result.success:
|
403
|
+
# Refresh payment from database
|
404
|
+
payment.refresh_from_db()
|
405
|
+
|
406
|
+
except Exception as e:
|
407
|
+
logger.warning(f"Provider status check failed: {e}")
|
408
|
+
|
409
|
+
serializer = self.get_serializer(payment)
|
410
|
+
return Response({
|
411
|
+
'success': True,
|
412
|
+
'payment': serializer.data,
|
413
|
+
'provider_checked': force_check,
|
414
|
+
'timestamp': timezone.now().isoformat()
|
415
|
+
})
|