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