django-cfg 1.2.31__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 -10
- 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 +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- 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 +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- 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 +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- 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 +172 -148
- 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 -285
- 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 +346 -467
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- 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 +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- 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 +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- 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 +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- 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/config.py +1 -1
- django_cfg/core/config.py +40 -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 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
- 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 -146
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
- django_cfg/apps/payments/managers/__init__.py +0 -23
- 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 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- 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 -63
- 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 -461
- 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 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- 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/admin/payments/currency/change_list.html +0 -50
- 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 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- 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 -63
- 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 -122
- 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 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- 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 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- 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 -66
- django_cfg/core/integration.py +0 -160
- 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.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,573 +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
|
-
from typing import Optional,
|
7
|
+
from typing import Optional, Dict, Any
|
9
8
|
from decimal import Decimal
|
10
|
-
from django.
|
11
|
-
from django.contrib.auth import get_user_model
|
9
|
+
from django.contrib.auth.models import User
|
12
10
|
from django.utils import timezone
|
13
|
-
from pydantic import BaseModel, Field, ValidationError
|
14
11
|
|
15
|
-
from .
|
16
|
-
from
|
17
|
-
|
18
|
-
|
19
|
-
from ..providers.registry import ProviderRegistry
|
20
|
-
from django_cfg.modules.django_logger import get_logger
|
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
|
-
from ...models.events import PaymentEvent
|
32
22
|
|
33
|
-
|
34
|
-
logger = get_logger("payment_service")
|
35
|
-
|
36
|
-
|
37
|
-
class PaymentRequest(BaseModel):
|
38
|
-
"""Type-safe payment request validation"""
|
39
|
-
user_id: int = Field(gt=0, description="User ID")
|
40
|
-
amount: Decimal = Field(gt=0, description="Payment amount")
|
41
|
-
currency: str = Field(min_length=3, max_length=10, description="Currency code")
|
42
|
-
provider: str = Field(min_length=1, description="Payment provider name")
|
43
|
-
callback_url: Optional[str] = Field(None, description="Success callback URL")
|
44
|
-
cancel_url: Optional[str] = Field(None, description="Cancellation URL")
|
45
|
-
metadata: dict = Field(default_factory=dict, description="Additional metadata")
|
46
|
-
|
47
|
-
|
48
|
-
class PaymentResult(BaseModel):
|
49
|
-
"""Type-safe payment operation result"""
|
50
|
-
success: bool
|
51
|
-
payment_id: Optional[str] = None
|
52
|
-
provider_payment_id: Optional[str] = None
|
53
|
-
payment_url: Optional[str] = None
|
54
|
-
error_message: Optional[str] = None
|
55
|
-
error_code: Optional[str] = None
|
56
|
-
metadata: dict = Field(default_factory=dict)
|
57
|
-
|
58
|
-
|
59
|
-
class WebhookProcessingResult(BaseModel):
|
60
|
-
"""Type-safe webhook processing result"""
|
61
|
-
success: bool
|
62
|
-
payment_id: Optional[str] = None
|
63
|
-
status_updated: bool = False
|
64
|
-
balance_updated: bool = False
|
65
|
-
error_message: Optional[str] = None
|
66
|
-
|
67
|
-
|
68
|
-
class PaymentService:
|
23
|
+
class PaymentService(BaseService):
|
69
24
|
"""
|
70
|
-
|
25
|
+
Payment service with business logic and validation.
|
71
26
|
|
72
|
-
Handles payment
|
73
|
-
Integrates with balance management and caching.
|
27
|
+
Handles payment operations using Pydantic validation and Django ORM managers.
|
74
28
|
"""
|
75
29
|
|
76
30
|
def __init__(self):
|
77
|
-
"""Initialize payment service with
|
78
|
-
|
79
|
-
|
31
|
+
"""Initialize payment service with configuration."""
|
32
|
+
super().__init__()
|
33
|
+
# Direct Constance access instead of ConfigService
|
34
|
+
self.provider_registry = get_provider_registry()
|
80
35
|
|
81
|
-
def create_payment(self,
|
36
|
+
def create_payment(self, request: PaymentCreateRequest) -> PaymentResult:
|
82
37
|
"""
|
83
|
-
Create
|
38
|
+
Create new payment with full validation.
|
84
39
|
|
85
40
|
Args:
|
86
|
-
|
41
|
+
request: Payment creation request with validation
|
87
42
|
|
88
43
|
Returns:
|
89
|
-
|
44
|
+
PaymentResult: Result with payment data or error
|
90
45
|
"""
|
91
46
|
try:
|
92
|
-
# Validate
|
93
|
-
request
|
94
|
-
|
95
|
-
amount=payment_data['amount'],
|
96
|
-
currency=payment_data.get('currency', 'USD'),
|
97
|
-
provider=payment_data['provider'],
|
98
|
-
metadata=payment_data.get('metadata', {})
|
99
|
-
)
|
47
|
+
# Validate request
|
48
|
+
if isinstance(request, dict):
|
49
|
+
request = PaymentCreateRequest(**request)
|
100
50
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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(
|
105
63
|
success=False,
|
106
|
-
|
64
|
+
message=f"User {request.user_id} not found",
|
65
|
+
error_code="user_not_found"
|
107
66
|
)
|
108
67
|
|
109
|
-
#
|
110
|
-
|
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
|
+
)
|
111
76
|
|
112
|
-
#
|
113
|
-
|
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
|
+
)
|
114
85
|
|
115
|
-
# Create payment
|
116
|
-
|
86
|
+
# Create payment in database first
|
87
|
+
def create_payment_transaction():
|
117
88
|
payment = UniversalPayment.objects.create(
|
118
89
|
user=user,
|
90
|
+
amount_usd=request.amount_usd,
|
91
|
+
currency_code=request.currency_code,
|
119
92
|
provider=request.provider,
|
120
|
-
amount_usd=amount_usd,
|
121
|
-
currency_code=request.currency,
|
122
93
|
status=UniversalPayment.PaymentStatus.PENDING,
|
123
|
-
|
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
|
124
99
|
)
|
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
|
-
|
154
|
-
payment.status = UniversalPayment.PaymentStatus.FAILED
|
155
|
-
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
|
156
129
|
payment.save()
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
169
149
|
)
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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()}
|
175
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())
|
176
168
|
|
177
|
-
def
|
178
|
-
self,
|
179
|
-
provider: str,
|
180
|
-
webhook_data: dict,
|
181
|
-
request_headers: Optional[dict] = None
|
182
|
-
) -> 'WebhookProcessingResult':
|
169
|
+
def get_payment_status(self, request: PaymentStatusRequest) -> PaymentResult:
|
183
170
|
"""
|
184
|
-
|
171
|
+
Get payment status with optional provider check.
|
185
172
|
|
186
173
|
Args:
|
187
|
-
|
188
|
-
webhook_data: Webhook payload data
|
189
|
-
request_headers: HTTP headers for validation
|
174
|
+
request: Payment status request
|
190
175
|
|
191
176
|
Returns:
|
192
|
-
|
177
|
+
PaymentResult: Current payment status
|
193
178
|
"""
|
194
179
|
try:
|
195
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
return WebhookProcessingResult(
|
199
|
-
success=False,
|
200
|
-
error=f"Provider '{provider}' not found"
|
201
|
-
)
|
180
|
+
# Validate request
|
181
|
+
if isinstance(request, dict):
|
182
|
+
request = PaymentStatusRequest(**request)
|
202
183
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
success=False,
|
208
|
-
error=webhook_result.error_message or "Webhook processing failed"
|
209
|
-
)
|
184
|
+
self.logger.debug("Getting payment status", extra={
|
185
|
+
'payment_id': request.payment_id,
|
186
|
+
'force_provider_check': request.force_provider_check
|
187
|
+
})
|
210
188
|
|
211
|
-
#
|
189
|
+
# Get payment
|
212
190
|
try:
|
213
|
-
payment = UniversalPayment.objects.get(
|
214
|
-
provider_payment_id=webhook_result.provider_payment_id
|
215
|
-
)
|
191
|
+
payment = UniversalPayment.objects.get(id=request.payment_id)
|
216
192
|
except UniversalPayment.DoesNotExist:
|
217
|
-
return
|
193
|
+
return PaymentResult(
|
218
194
|
success=False,
|
219
|
-
|
195
|
+
message=f"Payment {request.payment_id} not found",
|
196
|
+
error_code="payment_not_found"
|
220
197
|
)
|
221
198
|
|
222
|
-
#
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
payment.status = new_status
|
229
|
-
payment.save()
|
230
|
-
|
231
|
-
# Process completion if status changed to completed
|
232
|
-
balance_updated = False
|
233
|
-
if (new_status == UniversalPayment.PaymentStatus.COMPLETED and
|
234
|
-
old_status != UniversalPayment.PaymentStatus.COMPLETED):
|
235
|
-
balance_updated = self._process_payment_completion(payment)
|
236
|
-
|
237
|
-
|
238
|
-
return WebhookProcessingResult(
|
239
|
-
success=True,
|
240
|
-
payment_id=str(payment.id),
|
241
|
-
status_updated=(old_status != new_status),
|
242
|
-
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"
|
243
205
|
)
|
244
|
-
|
245
|
-
except Exception as e:
|
246
|
-
logger.error(f"Webhook processing failed for {provider}: {e}", exc_info=True)
|
247
|
-
return WebhookProcessingResult(
|
248
|
-
success=False,
|
249
|
-
error=f"Webhook processing error: {str(e)}"
|
250
|
-
)
|
251
|
-
|
252
|
-
def get_payment_status(self, payment_id: str) -> Optional['PaymentStatusResult']:
|
253
|
-
"""
|
254
|
-
Get payment status by ID.
|
255
|
-
|
256
|
-
Args:
|
257
|
-
payment_id: Payment UUID
|
258
206
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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()
|
263
213
|
|
264
|
-
#
|
265
|
-
|
214
|
+
# Convert to PaymentData
|
215
|
+
payment_data = PaymentData.model_validate(payment)
|
266
216
|
|
267
|
-
return
|
217
|
+
return PaymentResult(
|
218
|
+
success=True,
|
219
|
+
message="Payment status retrieved",
|
268
220
|
payment_id=str(payment.id),
|
269
221
|
status=payment.status,
|
270
222
|
amount_usd=payment.amount_usd,
|
223
|
+
crypto_amount=payment.crypto_amount,
|
271
224
|
currency_code=payment.currency_code,
|
272
|
-
provider=payment.provider,
|
273
225
|
provider_payment_id=payment.provider_payment_id,
|
274
|
-
|
275
|
-
|
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()}
|
276
231
|
)
|
277
232
|
|
278
|
-
except UniversalPayment.DoesNotExist:
|
279
|
-
return None
|
280
233
|
except Exception as e:
|
281
|
-
|
282
|
-
|
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())
|
283
238
|
|
284
|
-
def
|
285
|
-
self,
|
286
|
-
user: User,
|
287
|
-
status: Optional[str] = None,
|
288
|
-
limit: int = 50,
|
289
|
-
offset: int = 0
|
290
|
-
) -> List[PaymentHistoryItem]:
|
239
|
+
def cancel_payment(self, payment_id: str, reason: str = None) -> PaymentResult:
|
291
240
|
"""
|
292
|
-
|
241
|
+
Cancel payment if possible.
|
293
242
|
|
294
243
|
Args:
|
295
|
-
|
296
|
-
|
297
|
-
limit: Number of payments to return
|
298
|
-
offset: Pagination offset
|
244
|
+
payment_id: Payment ID to cancel
|
245
|
+
reason: Cancellation reason
|
299
246
|
|
300
247
|
Returns:
|
301
|
-
|
248
|
+
PaymentResult: Cancellation result
|
302
249
|
"""
|
303
250
|
try:
|
304
|
-
|
251
|
+
self.logger.info("Cancelling payment", extra={
|
252
|
+
'payment_id': payment_id,
|
253
|
+
'reason': reason
|
254
|
+
})
|
305
255
|
|
306
|
-
|
307
|
-
|
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
|
+
)
|
265
|
+
|
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)
|
308
277
|
|
309
|
-
|
278
|
+
success = self._execute_with_transaction(cancel_payment_transaction)
|
310
279
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
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),
|
317
295
|
status=payment.status,
|
318
|
-
|
319
|
-
provider_payment_id=payment.provider_payment_id,
|
320
|
-
created_at=payment.created_at,
|
321
|
-
updated_at=payment.updated_at,
|
322
|
-
metadata=payment.metadata or {}
|
296
|
+
data={'payment': payment_data.model_dump()}
|
323
297
|
)
|
324
|
-
|
325
|
-
|
326
|
-
|
298
|
+
else:
|
299
|
+
return PaymentResult(
|
300
|
+
success=False,
|
301
|
+
message="Failed to cancel payment",
|
302
|
+
error_code="cancel_failed"
|
303
|
+
)
|
304
|
+
|
327
305
|
except Exception as e:
|
328
|
-
|
329
|
-
|
306
|
+
return PaymentResult(**self._handle_exception(
|
307
|
+
"cancel_payment", e,
|
308
|
+
payment_id=payment_id
|
309
|
+
).model_dump())
|
330
310
|
|
331
|
-
def
|
332
|
-
"""
|
333
|
-
Process completed payment by adding funds to user balance.
|
334
|
-
|
335
|
-
Args:
|
336
|
-
payment: Completed payment object
|
337
|
-
|
338
|
-
Returns:
|
339
|
-
True if balance was updated, False otherwise
|
340
|
-
"""
|
311
|
+
def _validate_currency(self, currency_code: str) -> ServiceOperationResult:
|
312
|
+
"""Validate currency is supported."""
|
341
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
|
+
)
|
342
327
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
amount=payment.amount_usd,
|
347
|
-
currency_code='USD',
|
348
|
-
source='payment',
|
349
|
-
reference_id=str(payment.id),
|
350
|
-
metadata={
|
351
|
-
'provider': payment.provider.name if payment.provider else 'unknown',
|
352
|
-
'provider_payment_id': payment.provider_payment_id,
|
353
|
-
'pay_amount': str(payment.pay_amount) if payment.pay_amount else str(payment.amount_usd),
|
354
|
-
'currency_code': payment.currency_code
|
355
|
-
}
|
328
|
+
return self._create_success_result(
|
329
|
+
"Currency is valid",
|
330
|
+
{'currency': currency_code}
|
356
331
|
)
|
357
332
|
|
358
|
-
|
359
|
-
return
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
return False
|
333
|
+
except Currency.DoesNotExist:
|
334
|
+
return self._create_error_result(
|
335
|
+
f"Currency {currency_code} not found",
|
336
|
+
"currency_not_found"
|
337
|
+
)
|
364
338
|
|
365
|
-
def
|
366
|
-
"""
|
367
|
-
Convert amount to USD using django_currency module.
|
368
|
-
|
369
|
-
Args:
|
370
|
-
amount: Amount to convert
|
371
|
-
currency: Source currency
|
372
|
-
|
373
|
-
Returns:
|
374
|
-
Amount in USD
|
375
|
-
"""
|
376
|
-
if currency == 'USD':
|
377
|
-
return amount
|
378
|
-
|
339
|
+
def _convert_usd_to_crypto(self, amount_usd: float, currency_code: str) -> ServiceOperationResult:
|
340
|
+
"""Convert USD amount to cryptocurrency."""
|
379
341
|
try:
|
380
342
|
# Use django_currency module for conversion
|
381
|
-
|
382
|
-
amount=float(amount),
|
383
|
-
from_currency=currency,
|
384
|
-
to_currency='USD'
|
385
|
-
)
|
386
|
-
|
387
|
-
logger.info(f"Currency conversion: {amount} {currency} = {converted_amount} USD")
|
388
|
-
return Decimal(str(converted_amount))
|
343
|
+
crypto_amount = convert_currency(amount_usd, 'USD', currency_code)
|
389
344
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
+
)
|
395
354
|
|
396
355
|
except Exception as e:
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
356
|
+
return self._create_error_result(
|
357
|
+
f"Currency conversion failed: {e}",
|
358
|
+
"conversion_failed"
|
359
|
+
)
|
401
360
|
|
402
|
-
def
|
403
|
-
"""
|
404
|
-
Process webhook from payment provider.
|
405
|
-
|
406
|
-
Args:
|
407
|
-
provider: Provider name
|
408
|
-
webhook_data: Webhook payload
|
409
|
-
headers: Request headers for validation
|
410
|
-
|
411
|
-
Returns:
|
412
|
-
WebhookProcessingResult with processing status
|
413
|
-
"""
|
361
|
+
def _check_provider_status(self, payment: UniversalPayment) -> ServiceOperationResult:
|
362
|
+
"""Check payment status with provider."""
|
414
363
|
try:
|
415
|
-
#
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
)
|
422
|
-
|
423
|
-
# Validate webhook
|
424
|
-
if hasattr(provider_instance, 'validate_webhook'):
|
425
|
-
is_valid = provider_instance.validate_webhook(webhook_data, headers)
|
426
|
-
if not is_valid:
|
427
|
-
logger.warning(f"Invalid webhook from {provider}: {webhook_data}")
|
428
|
-
return WebhookProcessingResult(
|
429
|
-
success=False,
|
430
|
-
error_message="Webhook validation failed"
|
431
|
-
)
|
432
|
-
|
433
|
-
# Process webhook data
|
434
|
-
processed_data = provider_instance.process_webhook(webhook_data)
|
435
|
-
|
436
|
-
# Find payment record
|
437
|
-
payment_id = processed_data.payment_id
|
438
|
-
if not payment_id:
|
439
|
-
return WebhookProcessingResult(
|
440
|
-
success=False,
|
441
|
-
error_message="No payment ID found in webhook"
|
442
|
-
)
|
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
|
+
)
|
443
370
|
|
444
|
-
# Update payment
|
445
|
-
with transaction.atomic():
|
446
|
-
try:
|
447
|
-
payment = UniversalPayment.objects.get(
|
448
|
-
provider_payment_id=payment_id,
|
449
|
-
provider=provider
|
450
|
-
)
|
451
|
-
|
452
|
-
# Update payment status and data
|
453
|
-
old_status = payment.status
|
454
|
-
payment.update_from_webhook(webhook_data)
|
455
|
-
|
456
|
-
# Create event for audit trail
|
457
|
-
self._create_payment_event(
|
458
|
-
payment=payment,
|
459
|
-
event_type='webhook_processed',
|
460
|
-
data={
|
461
|
-
'provider': provider,
|
462
|
-
'old_status': old_status,
|
463
|
-
'new_status': payment.status,
|
464
|
-
'webhook_data': webhook_data
|
465
|
-
}
|
466
|
-
)
|
467
|
-
|
468
|
-
# Process completion if needed
|
469
|
-
if payment.is_completed and old_status != payment.status:
|
470
|
-
success = self._process_payment_completion(payment)
|
471
|
-
if success:
|
472
|
-
payment.processed_at = timezone.now()
|
473
|
-
payment.save()
|
474
|
-
|
475
|
-
return WebhookProcessingResult(
|
476
|
-
success=True,
|
477
|
-
payment_id=str(payment.id),
|
478
|
-
new_status=payment.status
|
479
|
-
)
|
480
|
-
|
481
|
-
except UniversalPayment.DoesNotExist:
|
482
|
-
logger.error(f"Payment not found for webhook: provider={provider}, payment_id={payment_id}")
|
483
|
-
return WebhookProcessingResult(
|
484
|
-
success=False,
|
485
|
-
error_message="Payment not found"
|
486
|
-
)
|
487
|
-
|
488
371
|
except Exception as e:
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
error_message=str(e)
|
372
|
+
return self._create_error_result(
|
373
|
+
f"Provider check failed: {e}",
|
374
|
+
"provider_check_failed"
|
493
375
|
)
|
494
376
|
|
495
|
-
def
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
"""
|
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."""
|
504
385
|
try:
|
505
|
-
|
506
|
-
last_event = PaymentEvent.objects.filter(
|
507
|
-
payment_id=str(payment.id)
|
508
|
-
).order_by('-sequence_number').first()
|
386
|
+
queryset = UniversalPayment.objects.filter(user_id=user_id)
|
509
387
|
|
510
|
-
|
511
|
-
|
512
|
-
PaymentEvent.objects.create(
|
513
|
-
payment_id=str(payment.id),
|
514
|
-
event_type=event_type,
|
515
|
-
sequence_number=sequence_number,
|
516
|
-
event_data=data,
|
517
|
-
processed_by=f"payment_service_{timezone.now().timestamp()}",
|
518
|
-
correlation_id=data.get('correlation_id'),
|
519
|
-
idempotency_key=f"{payment.id}_{event_type}_{sequence_number}"
|
520
|
-
)
|
388
|
+
if status:
|
389
|
+
queryset = queryset.filter(status=status)
|
521
390
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
def get_payment_events(self, payment_id: str) -> List[dict]:
|
526
|
-
"""
|
527
|
-
Get all events for a payment.
|
528
|
-
|
529
|
-
Args:
|
530
|
-
payment_id: Payment ID
|
391
|
+
total_count = queryset.count()
|
392
|
+
payments = queryset.order_by('-created_at')[offset:offset + limit]
|
531
393
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
events = PaymentEvent.objects.filter(
|
537
|
-
payment_id=payment_id
|
538
|
-
).order_by('sequence_number')
|
394
|
+
payment_data = [
|
395
|
+
PaymentData.model_validate(payment).model_dump()
|
396
|
+
for payment in payments
|
397
|
+
]
|
539
398
|
|
540
|
-
return
|
399
|
+
return self._create_success_result(
|
400
|
+
f"Retrieved {len(payment_data)} payments",
|
541
401
|
{
|
542
|
-
'
|
543
|
-
'
|
544
|
-
'
|
545
|
-
'
|
546
|
-
'
|
547
|
-
'processed_by': event.processed_by
|
402
|
+
'payments': payment_data,
|
403
|
+
'total_count': total_count,
|
404
|
+
'limit': limit,
|
405
|
+
'offset': offset,
|
406
|
+
'has_more': offset + limit < total_count
|
548
407
|
}
|
549
|
-
|
550
|
-
]
|
408
|
+
)
|
551
409
|
|
552
410
|
except Exception as e:
|
553
|
-
|
554
|
-
|
555
|
-
|
411
|
+
return self._handle_exception(
|
412
|
+
"get_user_payments", e,
|
413
|
+
user_id=user_id
|
414
|
+
)
|
556
415
|
|
557
|
-
def
|
558
|
-
"""
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
416
|
+
def get_payment_stats(self, days: int = 30) -> ServiceOperationResult:
|
417
|
+
"""Get payment statistics."""
|
418
|
+
try:
|
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
|
+
)
|
437
|
+
|
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
|
442
|
+
|
443
|
+
stats['success_rate'] = round(success_rate, 2)
|
444
|
+
stats['period_days'] = days
|
445
|
+
|
446
|
+
return self._create_success_result(
|
447
|
+
f"Payment statistics for {days} days",
|
448
|
+
stats
|
571
449
|
)
|
572
|
-
|
573
|
-
|
450
|
+
|
451
|
+
except Exception as e:
|
452
|
+
return self._handle_exception("get_payment_stats", e)
|