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
@@ -1,576 +1,452 @@
|
|
1
1
|
"""
|
2
|
-
Payment
|
2
|
+
Payment service for the Universal Payment System v2.0.
|
3
3
|
|
4
|
-
|
5
|
-
and payment lifecycle management.
|
4
|
+
Handles payment creation, status checking, and lifecycle management.
|
6
5
|
"""
|
7
6
|
|
8
|
-
import
|
9
|
-
from typing import Optional, List
|
7
|
+
from typing import Optional, Dict, Any
|
10
8
|
from decimal import Decimal
|
11
|
-
from django.
|
12
|
-
from django.contrib.auth import get_user_model
|
9
|
+
from django.contrib.auth.models import User
|
13
10
|
from django.utils import timezone
|
14
|
-
from pydantic import BaseModel, Field, ValidationError
|
15
11
|
|
16
|
-
from .
|
17
|
-
from
|
18
|
-
|
19
|
-
|
20
|
-
from ..providers.registry import ProviderRegistry
|
21
|
-
from ..monitoring.provider_health import get_health_monitor
|
22
|
-
from ..internal_types import (
|
23
|
-
ProviderResponse, WebhookData, ServiceOperationResult,
|
24
|
-
BalanceUpdateRequest, AccessCheckRequest, AccessCheckResult,
|
25
|
-
PaymentCreationResult, WebhookProcessingResult, PaymentStatusResult,
|
26
|
-
PaymentHistoryItem, ProviderInfo
|
12
|
+
from .base import BaseService
|
13
|
+
from ..types import (
|
14
|
+
PaymentCreateRequest, PaymentStatusRequest, PaymentResult,
|
15
|
+
PaymentData, ServiceOperationResult
|
27
16
|
)
|
17
|
+
from ...models import UniversalPayment, Currency, ProviderCurrency
|
18
|
+
from django_cfg.modules.django_currency import convert_currency, get_exchange_rate
|
19
|
+
# ConfigService removed - using direct Constance access
|
20
|
+
from ..providers import ProviderRegistry, get_provider_registry
|
28
21
|
|
29
|
-
# Import django_currency module for currency conversion
|
30
|
-
from django_cfg.modules.django_currency import convert_currency, CurrencyError
|
31
22
|
|
32
|
-
|
33
|
-
logger = logging.getLogger(__name__)
|
34
|
-
|
35
|
-
|
36
|
-
class PaymentRequest(BaseModel):
|
37
|
-
"""Type-safe payment request validation"""
|
38
|
-
user_id: int = Field(gt=0, description="User ID")
|
39
|
-
amount: Decimal = Field(gt=0, description="Payment amount")
|
40
|
-
currency: str = Field(min_length=3, max_length=10, description="Currency code")
|
41
|
-
provider: str = Field(min_length=1, description="Payment provider name")
|
42
|
-
callback_url: Optional[str] = Field(None, description="Success callback URL")
|
43
|
-
cancel_url: Optional[str] = Field(None, description="Cancellation URL")
|
44
|
-
metadata: dict = Field(default_factory=dict, description="Additional metadata")
|
45
|
-
|
46
|
-
|
47
|
-
class PaymentResult(BaseModel):
|
48
|
-
"""Type-safe payment operation result"""
|
49
|
-
success: bool
|
50
|
-
payment_id: Optional[str] = None
|
51
|
-
provider_payment_id: Optional[str] = None
|
52
|
-
payment_url: Optional[str] = None
|
53
|
-
error_message: Optional[str] = None
|
54
|
-
error_code: Optional[str] = None
|
55
|
-
metadata: dict = Field(default_factory=dict)
|
56
|
-
|
57
|
-
|
58
|
-
class WebhookProcessingResult(BaseModel):
|
59
|
-
"""Type-safe webhook processing result"""
|
60
|
-
success: bool
|
61
|
-
payment_id: Optional[str] = None
|
62
|
-
status_updated: bool = False
|
63
|
-
balance_updated: bool = False
|
64
|
-
error_message: Optional[str] = None
|
65
|
-
|
66
|
-
|
67
|
-
class PaymentService:
|
23
|
+
class PaymentService(BaseService):
|
68
24
|
"""
|
69
|
-
|
25
|
+
Payment service with business logic and validation.
|
70
26
|
|
71
|
-
Handles payment
|
72
|
-
Integrates with balance management and caching.
|
27
|
+
Handles payment operations using Pydantic validation and Django ORM managers.
|
73
28
|
"""
|
74
29
|
|
75
30
|
def __init__(self):
|
76
|
-
"""Initialize payment service with
|
77
|
-
|
78
|
-
|
31
|
+
"""Initialize payment service with configuration."""
|
32
|
+
super().__init__()
|
33
|
+
# Direct Constance access instead of ConfigService
|
34
|
+
self.provider_registry = get_provider_registry()
|
79
35
|
|
80
|
-
def create_payment(self,
|
36
|
+
def create_payment(self, request: PaymentCreateRequest) -> PaymentResult:
|
81
37
|
"""
|
82
|
-
Create
|
38
|
+
Create new payment with full validation.
|
83
39
|
|
84
40
|
Args:
|
85
|
-
|
41
|
+
request: Payment creation request with validation
|
86
42
|
|
87
43
|
Returns:
|
88
|
-
|
44
|
+
PaymentResult: Result with payment data or error
|
89
45
|
"""
|
90
46
|
try:
|
91
|
-
# Validate
|
92
|
-
request
|
93
|
-
|
94
|
-
amount=payment_data['amount'],
|
95
|
-
currency=payment_data.get('currency', 'USD'),
|
96
|
-
provider=payment_data['provider'],
|
97
|
-
metadata=payment_data.get('metadata', {})
|
98
|
-
)
|
47
|
+
# Validate request
|
48
|
+
if isinstance(request, dict):
|
49
|
+
request = PaymentCreateRequest(**request)
|
99
50
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
51
|
+
self.logger.info("Creating payment", extra={
|
52
|
+
'user_id': request.user_id,
|
53
|
+
'amount_usd': request.amount_usd,
|
54
|
+
'currency_code': request.currency_code,
|
55
|
+
'provider': request.provider
|
56
|
+
})
|
57
|
+
|
58
|
+
# Get user
|
59
|
+
try:
|
60
|
+
user = User.objects.get(id=request.user_id)
|
61
|
+
except User.DoesNotExist:
|
62
|
+
return PaymentResult(
|
104
63
|
success=False,
|
105
|
-
|
64
|
+
message=f"User {request.user_id} not found",
|
65
|
+
error_code="user_not_found"
|
106
66
|
)
|
107
67
|
|
108
|
-
#
|
109
|
-
|
68
|
+
# Validate currency
|
69
|
+
currency_result = self._validate_currency(request.currency_code)
|
70
|
+
if not currency_result.success:
|
71
|
+
return PaymentResult(
|
72
|
+
success=False,
|
73
|
+
message=currency_result.message,
|
74
|
+
error_code=currency_result.error_code
|
75
|
+
)
|
110
76
|
|
111
|
-
#
|
112
|
-
|
77
|
+
# Get provider for payment creation
|
78
|
+
provider = self.provider_registry.get_provider(request.provider)
|
79
|
+
if not provider:
|
80
|
+
return PaymentResult(
|
81
|
+
success=False,
|
82
|
+
message=f"Provider {request.provider} not available",
|
83
|
+
error_code="provider_not_available"
|
84
|
+
)
|
113
85
|
|
114
|
-
# Create payment
|
115
|
-
|
86
|
+
# Create payment in database first
|
87
|
+
def create_payment_transaction():
|
116
88
|
payment = UniversalPayment.objects.create(
|
117
89
|
user=user,
|
90
|
+
amount_usd=request.amount_usd,
|
91
|
+
currency_code=request.currency_code,
|
118
92
|
provider=request.provider,
|
119
|
-
amount_usd=amount_usd,
|
120
|
-
currency_code=request.currency,
|
121
93
|
status=UniversalPayment.PaymentStatus.PENDING,
|
122
|
-
|
94
|
+
callback_url=request.callback_url,
|
95
|
+
cancel_url=request.cancel_url,
|
96
|
+
description=request.description,
|
97
|
+
metadata=request.metadata,
|
98
|
+
expires_at=timezone.now() + timezone.timedelta(hours=1) # 1 hour expiry
|
123
99
|
)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
payment.status = UniversalPayment.PaymentStatus.FAILED
|
154
|
-
payment.error_message = provider_result.error_message or 'Unknown provider error'
|
100
|
+
return payment
|
101
|
+
|
102
|
+
payment = self._execute_with_transaction(create_payment_transaction)
|
103
|
+
|
104
|
+
# Create payment with provider
|
105
|
+
from ..providers.base import PaymentRequest as ProviderPaymentRequest
|
106
|
+
|
107
|
+
provider_request = ProviderPaymentRequest(
|
108
|
+
amount_usd=request.amount_usd,
|
109
|
+
currency_code=request.currency_code,
|
110
|
+
order_id=str(payment.id),
|
111
|
+
callback_url=request.callback_url,
|
112
|
+
cancel_url=request.cancel_url,
|
113
|
+
description=request.description,
|
114
|
+
metadata=request.metadata
|
115
|
+
)
|
116
|
+
|
117
|
+
provider_response = provider.create_payment(provider_request)
|
118
|
+
|
119
|
+
# Update payment with provider response
|
120
|
+
if provider_response.success:
|
121
|
+
def update_payment_transaction():
|
122
|
+
payment.provider_payment_id = provider_response.provider_payment_id
|
123
|
+
payment.crypto_amount = provider_response.amount
|
124
|
+
payment.payment_url = provider_response.payment_url
|
125
|
+
payment.qr_code_url = provider_response.qr_code_url
|
126
|
+
payment.wallet_address = provider_response.wallet_address
|
127
|
+
if provider_response.expires_at:
|
128
|
+
payment.expires_at = provider_response.expires_at
|
155
129
|
payment.save()
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
130
|
+
return payment
|
131
|
+
|
132
|
+
payment = self._execute_with_transaction(update_payment_transaction)
|
133
|
+
else:
|
134
|
+
# Mark payment as failed if provider creation failed
|
135
|
+
payment.mark_failed(
|
136
|
+
reason=provider_response.error_message,
|
137
|
+
error_code="provider_creation_failed"
|
138
|
+
)
|
139
|
+
|
140
|
+
# Convert to PaymentData for response
|
141
|
+
payment_data = PaymentData.model_validate(payment)
|
142
|
+
|
143
|
+
self._log_operation(
|
144
|
+
"create_payment",
|
145
|
+
True,
|
146
|
+
payment_id=str(payment.id),
|
147
|
+
user_id=request.user_id,
|
148
|
+
amount_usd=request.amount_usd
|
168
149
|
)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
150
|
+
|
151
|
+
return PaymentResult(
|
152
|
+
success=True,
|
153
|
+
message="Payment created successfully",
|
154
|
+
payment_id=str(payment.id),
|
155
|
+
status=payment.status,
|
156
|
+
amount_usd=payment.amount_usd,
|
157
|
+
crypto_amount=payment.crypto_amount,
|
158
|
+
currency_code=payment.currency_code,
|
159
|
+
expires_at=payment.expires_at,
|
160
|
+
data={'payment': payment_data.model_dump()}
|
174
161
|
)
|
162
|
+
|
163
|
+
except Exception as e:
|
164
|
+
return PaymentResult(**self._handle_exception(
|
165
|
+
"create_payment", e,
|
166
|
+
user_id=request.user_id if hasattr(request, 'user_id') else None
|
167
|
+
).model_dump())
|
175
168
|
|
176
|
-
def
|
177
|
-
self,
|
178
|
-
provider: str,
|
179
|
-
webhook_data: dict,
|
180
|
-
request_headers: Optional[dict] = None
|
181
|
-
) -> 'WebhookProcessingResult':
|
169
|
+
def get_payment_status(self, request: PaymentStatusRequest) -> PaymentResult:
|
182
170
|
"""
|
183
|
-
|
171
|
+
Get payment status with optional provider check.
|
184
172
|
|
185
173
|
Args:
|
186
|
-
|
187
|
-
webhook_data: Webhook payload data
|
188
|
-
request_headers: HTTP headers for validation
|
174
|
+
request: Payment status request
|
189
175
|
|
190
176
|
Returns:
|
191
|
-
|
177
|
+
PaymentResult: Current payment status
|
192
178
|
"""
|
193
179
|
try:
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
return WebhookProcessingResult(
|
198
|
-
success=False,
|
199
|
-
error=f"Provider '{provider}' not found"
|
200
|
-
)
|
180
|
+
# Validate request
|
181
|
+
if isinstance(request, dict):
|
182
|
+
request = PaymentStatusRequest(**request)
|
201
183
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
success=False,
|
207
|
-
error=webhook_result.error_message or "Webhook processing failed"
|
208
|
-
)
|
184
|
+
self.logger.debug("Getting payment status", extra={
|
185
|
+
'payment_id': request.payment_id,
|
186
|
+
'force_provider_check': request.force_provider_check
|
187
|
+
})
|
209
188
|
|
210
|
-
#
|
189
|
+
# Get payment
|
211
190
|
try:
|
212
|
-
payment = UniversalPayment.objects.get(
|
213
|
-
provider_payment_id=webhook_result.provider_payment_id
|
214
|
-
)
|
191
|
+
payment = UniversalPayment.objects.get(id=request.payment_id)
|
215
192
|
except UniversalPayment.DoesNotExist:
|
216
|
-
return
|
193
|
+
return PaymentResult(
|
217
194
|
success=False,
|
218
|
-
|
195
|
+
message=f"Payment {request.payment_id} not found",
|
196
|
+
error_code="payment_not_found"
|
219
197
|
)
|
220
198
|
|
221
|
-
#
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
payment.status = new_status
|
228
|
-
payment.save()
|
229
|
-
|
230
|
-
# Process completion if status changed to completed
|
231
|
-
balance_updated = False
|
232
|
-
if (new_status == UniversalPayment.PaymentStatus.COMPLETED and
|
233
|
-
old_status != UniversalPayment.PaymentStatus.COMPLETED):
|
234
|
-
balance_updated = self._process_payment_completion(payment)
|
235
|
-
|
236
|
-
|
237
|
-
return WebhookProcessingResult(
|
238
|
-
success=True,
|
239
|
-
payment_id=str(payment.id),
|
240
|
-
status_updated=(old_status != new_status),
|
241
|
-
balance_updated=balance_updated
|
199
|
+
# Check user authorization if provided
|
200
|
+
if request.user_id and payment.user_id != request.user_id:
|
201
|
+
return PaymentResult(
|
202
|
+
success=False,
|
203
|
+
message="Access denied to payment",
|
204
|
+
error_code="access_denied"
|
242
205
|
)
|
243
|
-
|
244
|
-
except Exception as e:
|
245
|
-
logger.error(f"Webhook processing failed for {provider}: {e}", exc_info=True)
|
246
|
-
return WebhookProcessingResult(
|
247
|
-
success=False,
|
248
|
-
error=f"Webhook processing error: {str(e)}"
|
249
|
-
)
|
250
|
-
|
251
|
-
def get_payment_status(self, payment_id: str) -> Optional['PaymentStatusResult']:
|
252
|
-
"""
|
253
|
-
Get payment status by ID.
|
254
|
-
|
255
|
-
Args:
|
256
|
-
payment_id: Payment UUID
|
257
206
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
207
|
+
# Force provider check if requested
|
208
|
+
if request.force_provider_check:
|
209
|
+
provider_result = self._check_provider_status(payment)
|
210
|
+
if provider_result.success and provider_result.data.get('status_changed'):
|
211
|
+
# Reload payment if status was updated
|
212
|
+
payment.refresh_from_db()
|
262
213
|
|
263
|
-
#
|
264
|
-
|
214
|
+
# Convert to PaymentData
|
215
|
+
payment_data = PaymentData.model_validate(payment)
|
265
216
|
|
266
|
-
return
|
217
|
+
return PaymentResult(
|
218
|
+
success=True,
|
219
|
+
message="Payment status retrieved",
|
267
220
|
payment_id=str(payment.id),
|
268
221
|
status=payment.status,
|
269
222
|
amount_usd=payment.amount_usd,
|
223
|
+
crypto_amount=payment.crypto_amount,
|
270
224
|
currency_code=payment.currency_code,
|
271
|
-
provider=payment.provider,
|
272
225
|
provider_payment_id=payment.provider_payment_id,
|
273
|
-
|
274
|
-
|
226
|
+
payment_url=payment.payment_url,
|
227
|
+
qr_code_url=payment.qr_code_url,
|
228
|
+
wallet_address=payment.wallet_address,
|
229
|
+
expires_at=payment.expires_at,
|
230
|
+
data={'payment': payment_data.model_dump()}
|
275
231
|
)
|
276
232
|
|
277
|
-
except UniversalPayment.DoesNotExist:
|
278
|
-
return None
|
279
233
|
except Exception as e:
|
280
|
-
|
281
|
-
|
234
|
+
return PaymentResult(**self._handle_exception(
|
235
|
+
"get_payment_status", e,
|
236
|
+
payment_id=request.payment_id if hasattr(request, 'payment_id') else None
|
237
|
+
).model_dump())
|
282
238
|
|
283
|
-
def
|
284
|
-
self,
|
285
|
-
user: User,
|
286
|
-
status: Optional[str] = None,
|
287
|
-
limit: int = 50,
|
288
|
-
offset: int = 0
|
289
|
-
) -> List[PaymentHistoryItem]:
|
239
|
+
def cancel_payment(self, payment_id: str, reason: str = None) -> PaymentResult:
|
290
240
|
"""
|
291
|
-
|
241
|
+
Cancel payment if possible.
|
292
242
|
|
293
243
|
Args:
|
294
|
-
|
295
|
-
|
296
|
-
limit: Number of payments to return
|
297
|
-
offset: Pagination offset
|
244
|
+
payment_id: Payment ID to cancel
|
245
|
+
reason: Cancellation reason
|
298
246
|
|
299
247
|
Returns:
|
300
|
-
|
248
|
+
PaymentResult: Cancellation result
|
301
249
|
"""
|
302
250
|
try:
|
303
|
-
|
251
|
+
self.logger.info("Cancelling payment", extra={
|
252
|
+
'payment_id': payment_id,
|
253
|
+
'reason': reason
|
254
|
+
})
|
304
255
|
|
305
|
-
|
306
|
-
|
256
|
+
# Get payment
|
257
|
+
try:
|
258
|
+
payment = UniversalPayment.objects.get(id=payment_id)
|
259
|
+
except UniversalPayment.DoesNotExist:
|
260
|
+
return PaymentResult(
|
261
|
+
success=False,
|
262
|
+
message=f"Payment {payment_id} not found",
|
263
|
+
error_code="payment_not_found"
|
264
|
+
)
|
307
265
|
|
308
|
-
|
266
|
+
# Check if payment can be cancelled
|
267
|
+
if not payment.can_be_cancelled():
|
268
|
+
return PaymentResult(
|
269
|
+
success=False,
|
270
|
+
message=f"Payment {payment_id} cannot be cancelled (status: {payment.status})",
|
271
|
+
error_code="cannot_cancel"
|
272
|
+
)
|
273
|
+
|
274
|
+
# Cancel using manager
|
275
|
+
def cancel_payment_transaction():
|
276
|
+
return payment.cancel(reason)
|
309
277
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
278
|
+
success = self._execute_with_transaction(cancel_payment_transaction)
|
279
|
+
|
280
|
+
if success:
|
281
|
+
payment.refresh_from_db()
|
282
|
+
payment_data = PaymentData.model_validate(payment)
|
283
|
+
|
284
|
+
self._log_operation(
|
285
|
+
"cancel_payment",
|
286
|
+
True,
|
287
|
+
payment_id=payment_id,
|
288
|
+
reason=reason
|
289
|
+
)
|
290
|
+
|
291
|
+
return PaymentResult(
|
292
|
+
success=True,
|
293
|
+
message="Payment cancelled successfully",
|
294
|
+
payment_id=str(payment.id),
|
316
295
|
status=payment.status,
|
317
|
-
|
318
|
-
provider_payment_id=payment.provider_payment_id,
|
319
|
-
created_at=payment.created_at,
|
320
|
-
updated_at=payment.updated_at,
|
321
|
-
metadata=payment.metadata or {}
|
296
|
+
data={'payment': payment_data.model_dump()}
|
322
297
|
)
|
323
|
-
|
324
|
-
|
325
|
-
|
298
|
+
else:
|
299
|
+
return PaymentResult(
|
300
|
+
success=False,
|
301
|
+
message="Failed to cancel payment",
|
302
|
+
error_code="cancel_failed"
|
303
|
+
)
|
304
|
+
|
326
305
|
except Exception as e:
|
327
|
-
|
328
|
-
|
306
|
+
return PaymentResult(**self._handle_exception(
|
307
|
+
"cancel_payment", e,
|
308
|
+
payment_id=payment_id
|
309
|
+
).model_dump())
|
329
310
|
|
330
|
-
def
|
331
|
-
"""
|
332
|
-
Process completed payment by adding funds to user balance.
|
333
|
-
|
334
|
-
Args:
|
335
|
-
payment: Completed payment object
|
336
|
-
|
337
|
-
Returns:
|
338
|
-
True if balance was updated, False otherwise
|
339
|
-
"""
|
311
|
+
def _validate_currency(self, currency_code: str) -> ServiceOperationResult:
|
312
|
+
"""Validate currency is supported."""
|
340
313
|
try:
|
314
|
+
currency = Currency.objects.get(code=currency_code, is_enabled=True)
|
315
|
+
|
316
|
+
# Check if currency is supported by any provider
|
317
|
+
provider_currency = ProviderCurrency.objects.filter(
|
318
|
+
currency=currency,
|
319
|
+
is_enabled=True
|
320
|
+
).first()
|
321
|
+
|
322
|
+
if not provider_currency:
|
323
|
+
return self._create_error_result(
|
324
|
+
f"Currency {currency_code} not supported by any provider",
|
325
|
+
"currency_not_supported"
|
326
|
+
)
|
341
327
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
amount=payment.amount_usd,
|
346
|
-
currency_code='USD',
|
347
|
-
source='payment',
|
348
|
-
reference_id=str(payment.id),
|
349
|
-
metadata={
|
350
|
-
'provider': payment.provider.name if payment.provider else 'unknown',
|
351
|
-
'provider_payment_id': payment.provider_payment_id,
|
352
|
-
'pay_amount': str(payment.pay_amount) if payment.pay_amount else str(payment.amount_usd),
|
353
|
-
'currency_code': payment.currency_code
|
354
|
-
}
|
328
|
+
return self._create_success_result(
|
329
|
+
"Currency is valid",
|
330
|
+
{'currency': currency_code}
|
355
331
|
)
|
356
332
|
|
357
|
-
|
358
|
-
return
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
return False
|
333
|
+
except Currency.DoesNotExist:
|
334
|
+
return self._create_error_result(
|
335
|
+
f"Currency {currency_code} not found",
|
336
|
+
"currency_not_found"
|
337
|
+
)
|
363
338
|
|
364
|
-
def
|
365
|
-
"""
|
366
|
-
Convert amount to USD using django_currency module.
|
367
|
-
|
368
|
-
Args:
|
369
|
-
amount: Amount to convert
|
370
|
-
currency: Source currency
|
371
|
-
|
372
|
-
Returns:
|
373
|
-
Amount in USD
|
374
|
-
"""
|
375
|
-
if currency == 'USD':
|
376
|
-
return amount
|
377
|
-
|
339
|
+
def _convert_usd_to_crypto(self, amount_usd: float, currency_code: str) -> ServiceOperationResult:
|
340
|
+
"""Convert USD amount to cryptocurrency."""
|
378
341
|
try:
|
379
342
|
# Use django_currency module for conversion
|
380
|
-
|
381
|
-
amount=float(amount),
|
382
|
-
from_currency=currency,
|
383
|
-
to_currency='USD'
|
384
|
-
)
|
343
|
+
crypto_amount = convert_currency(amount_usd, 'USD', currency_code)
|
385
344
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
345
|
+
return self._create_success_result(
|
346
|
+
"Currency converted successfully",
|
347
|
+
{
|
348
|
+
'amount_usd': amount_usd,
|
349
|
+
'crypto_amount': Decimal(str(crypto_amount)),
|
350
|
+
'currency_code': currency_code,
|
351
|
+
'exchange_rate': get_exchange_rate('USD', currency_code)
|
352
|
+
}
|
353
|
+
)
|
394
354
|
|
395
355
|
except Exception as e:
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
356
|
+
return self._create_error_result(
|
357
|
+
f"Currency conversion failed: {e}",
|
358
|
+
"conversion_failed"
|
359
|
+
)
|
400
360
|
|
401
|
-
def
|
402
|
-
"""
|
403
|
-
Process webhook from payment provider.
|
404
|
-
|
405
|
-
Args:
|
406
|
-
provider: Provider name
|
407
|
-
webhook_data: Webhook payload
|
408
|
-
headers: Request headers for validation
|
409
|
-
|
410
|
-
Returns:
|
411
|
-
WebhookProcessingResult with processing status
|
412
|
-
"""
|
361
|
+
def _check_provider_status(self, payment: UniversalPayment) -> ServiceOperationResult:
|
362
|
+
"""Check payment status with provider."""
|
413
363
|
try:
|
414
|
-
#
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
)
|
421
|
-
|
422
|
-
# Validate webhook
|
423
|
-
if hasattr(provider_instance, 'validate_webhook'):
|
424
|
-
is_valid = provider_instance.validate_webhook(webhook_data, headers)
|
425
|
-
if not is_valid:
|
426
|
-
logger.warning(f"Invalid webhook from {provider}: {webhook_data}")
|
427
|
-
return WebhookProcessingResult(
|
428
|
-
success=False,
|
429
|
-
error_message="Webhook validation failed"
|
430
|
-
)
|
431
|
-
|
432
|
-
# Process webhook data
|
433
|
-
processed_data = provider_instance.process_webhook(webhook_data)
|
434
|
-
|
435
|
-
# Find payment record
|
436
|
-
payment_id = processed_data.payment_id
|
437
|
-
if not payment_id:
|
438
|
-
return WebhookProcessingResult(
|
439
|
-
success=False,
|
440
|
-
error_message="No payment ID found in webhook"
|
441
|
-
)
|
364
|
+
# This would integrate with provider services
|
365
|
+
# For now, return success without changes
|
366
|
+
return self._create_success_result(
|
367
|
+
"Provider status checked",
|
368
|
+
{'status_changed': False}
|
369
|
+
)
|
442
370
|
|
443
|
-
# Update payment
|
444
|
-
with transaction.atomic():
|
445
|
-
try:
|
446
|
-
payment = UniversalPayment.objects.get(
|
447
|
-
provider_payment_id=payment_id,
|
448
|
-
provider=provider
|
449
|
-
)
|
450
|
-
|
451
|
-
# Update payment status and data
|
452
|
-
old_status = payment.status
|
453
|
-
payment.update_from_webhook(webhook_data)
|
454
|
-
|
455
|
-
# Create event for audit trail
|
456
|
-
self._create_payment_event(
|
457
|
-
payment=payment,
|
458
|
-
event_type='webhook_processed',
|
459
|
-
data={
|
460
|
-
'provider': provider,
|
461
|
-
'old_status': old_status,
|
462
|
-
'new_status': payment.status,
|
463
|
-
'webhook_data': webhook_data
|
464
|
-
}
|
465
|
-
)
|
466
|
-
|
467
|
-
# Process completion if needed
|
468
|
-
if payment.is_completed and old_status != payment.status:
|
469
|
-
success = self._process_payment_completion(payment)
|
470
|
-
if success:
|
471
|
-
payment.processed_at = timezone.now()
|
472
|
-
payment.save()
|
473
|
-
|
474
|
-
return WebhookProcessingResult(
|
475
|
-
success=True,
|
476
|
-
payment_id=str(payment.id),
|
477
|
-
new_status=payment.status
|
478
|
-
)
|
479
|
-
|
480
|
-
except UniversalPayment.DoesNotExist:
|
481
|
-
logger.error(f"Payment not found for webhook: provider={provider}, payment_id={payment_id}")
|
482
|
-
return WebhookProcessingResult(
|
483
|
-
success=False,
|
484
|
-
error_message="Payment not found"
|
485
|
-
)
|
486
|
-
|
487
371
|
except Exception as e:
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
error_message=str(e)
|
372
|
+
return self._create_error_result(
|
373
|
+
f"Provider check failed: {e}",
|
374
|
+
"provider_check_failed"
|
492
375
|
)
|
493
376
|
|
494
|
-
def
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
"""
|
377
|
+
def get_user_payments(
|
378
|
+
self,
|
379
|
+
user_id: int,
|
380
|
+
status: Optional[str] = None,
|
381
|
+
limit: int = 50,
|
382
|
+
offset: int = 0
|
383
|
+
) -> ServiceOperationResult:
|
384
|
+
"""Get user payments with pagination."""
|
503
385
|
try:
|
504
|
-
|
386
|
+
queryset = UniversalPayment.objects.filter(user_id=user_id)
|
505
387
|
|
506
|
-
|
507
|
-
|
508
|
-
payment_id=str(payment.id)
|
509
|
-
).order_by('-sequence_number').first()
|
388
|
+
if status:
|
389
|
+
queryset = queryset.filter(status=status)
|
510
390
|
|
511
|
-
|
391
|
+
total_count = queryset.count()
|
392
|
+
payments = queryset.order_by('-created_at')[offset:offset + limit]
|
512
393
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
394
|
+
payment_data = [
|
395
|
+
PaymentData.model_validate(payment).model_dump()
|
396
|
+
for payment in payments
|
397
|
+
]
|
398
|
+
|
399
|
+
return self._create_success_result(
|
400
|
+
f"Retrieved {len(payment_data)} payments",
|
401
|
+
{
|
402
|
+
'payments': payment_data,
|
403
|
+
'total_count': total_count,
|
404
|
+
'limit': limit,
|
405
|
+
'offset': offset,
|
406
|
+
'has_more': offset + limit < total_count
|
407
|
+
}
|
521
408
|
)
|
522
409
|
|
523
410
|
except Exception as e:
|
524
|
-
|
411
|
+
return self._handle_exception(
|
412
|
+
"get_user_payments", e,
|
413
|
+
user_id=user_id
|
414
|
+
)
|
525
415
|
|
526
|
-
def
|
527
|
-
"""
|
528
|
-
Get all events for a payment.
|
529
|
-
|
530
|
-
Args:
|
531
|
-
payment_id: Payment ID
|
532
|
-
|
533
|
-
Returns:
|
534
|
-
List of payment events
|
535
|
-
"""
|
416
|
+
def get_payment_stats(self, days: int = 30) -> ServiceOperationResult:
|
417
|
+
"""Get payment statistics."""
|
536
418
|
try:
|
537
|
-
from
|
419
|
+
from datetime import timedelta
|
420
|
+
|
421
|
+
since = timezone.now() - timedelta(days=days)
|
422
|
+
|
423
|
+
stats = UniversalPayment.objects.filter(
|
424
|
+
created_at__gte=since
|
425
|
+
).aggregate(
|
426
|
+
total_payments=models.Count('id'),
|
427
|
+
total_amount_usd=models.Sum('amount_usd'),
|
428
|
+
completed_payments=models.Count(
|
429
|
+
'id',
|
430
|
+
filter=models.Q(status=UniversalPayment.PaymentStatus.COMPLETED)
|
431
|
+
),
|
432
|
+
failed_payments=models.Count(
|
433
|
+
'id',
|
434
|
+
filter=models.Q(status=UniversalPayment.PaymentStatus.FAILED)
|
435
|
+
)
|
436
|
+
)
|
538
437
|
|
539
|
-
|
540
|
-
|
541
|
-
|
438
|
+
# Calculate success rate
|
439
|
+
total = stats['total_payments'] or 0
|
440
|
+
completed = stats['completed_payments'] or 0
|
441
|
+
success_rate = (completed / total * 100) if total > 0 else 0
|
542
442
|
|
543
|
-
|
544
|
-
|
545
|
-
'id': str(event.id),
|
546
|
-
'event_type': event.event_type,
|
547
|
-
'sequence_number': event.sequence_number,
|
548
|
-
'event_data': event.event_data,
|
549
|
-
'created_at': event.created_at,
|
550
|
-
'processed_by': event.processed_by
|
551
|
-
}
|
552
|
-
for event in events
|
553
|
-
]
|
443
|
+
stats['success_rate'] = round(success_rate, 2)
|
444
|
+
stats['period_days'] = days
|
554
445
|
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
def list_available_providers(self) -> List[ProviderInfo]:
|
561
|
-
"""
|
562
|
-
List all available payment providers.
|
563
|
-
|
564
|
-
Returns:
|
565
|
-
List of ProviderInfo objects
|
566
|
-
"""
|
567
|
-
return [
|
568
|
-
ProviderInfo(
|
569
|
-
name=name,
|
570
|
-
display_name=provider.get_display_name(),
|
571
|
-
supported_currencies=provider.get_supported_currencies(),
|
572
|
-
is_active=provider.is_active(),
|
573
|
-
features={'provider_type': provider.get_provider_type()}
|
446
|
+
return self._create_success_result(
|
447
|
+
f"Payment statistics for {days} days",
|
448
|
+
stats
|
574
449
|
)
|
575
|
-
|
576
|
-
|
450
|
+
|
451
|
+
except Exception as e:
|
452
|
+
return self._handle_exception("get_payment_stats", e)
|