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.
Files changed (258) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -9
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {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 Service - Core payment processing logic.
2
+ Payment service for the Universal Payment System v2.0.
3
3
 
4
- This service handles universal payment operations, provider orchestration,
5
- and payment lifecycle management.
4
+ Handles payment creation, status checking, and lifecycle management.
6
5
  """
7
6
 
8
- import logging
9
- from typing import Optional, List
7
+ from typing import Optional, Dict, Any
10
8
  from decimal import Decimal
11
- from django.db import transaction
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 .balance_service import BalanceService
17
- from .fallback_service import get_fallback_service
18
- from ...models import UniversalPayment, UserBalance, Transaction
19
- from ...utils.config_utils import get_payments_config
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
- User = get_user_model()
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
- Universal payment processing service.
25
+ Payment service with business logic and validation.
70
26
 
71
- Handles payment creation, webhook processing, and provider management.
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 dependencies"""
77
- self.provider_registry = ProviderRegistry()
78
- self.config = get_payments_config()
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, payment_data: dict) -> 'PaymentCreationResult':
36
+ def create_payment(self, request: PaymentCreateRequest) -> PaymentResult:
81
37
  """
82
- Create a new payment with the specified provider.
38
+ Create new payment with full validation.
83
39
 
84
40
  Args:
85
- payment_data: Dictionary with payment details
41
+ request: Payment creation request with validation
86
42
 
87
43
  Returns:
88
- PaymentCreationResult with payment details or error information
44
+ PaymentResult: Result with payment data or error
89
45
  """
90
46
  try:
91
- # Validate payment request
92
- request = PaymentRequest(
93
- user_id=payment_data['user_id'],
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
- # Get provider instance
101
- provider_instance = self.provider_registry.get_provider(request.provider)
102
- if not provider_instance:
103
- return PaymentCreationResult(
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
- error=f"Payment provider '{request.provider}' is not available"
64
+ message=f"User {request.user_id} not found",
65
+ error_code="user_not_found"
106
66
  )
107
67
 
108
- # Get user
109
- user = User.objects.get(id=request.user_id)
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
- # Convert currency if needed
112
- amount_usd = self._convert_to_usd(request.amount, request.currency) if request.currency != 'USD' else request.amount
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 record
115
- with transaction.atomic():
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
- metadata=request.metadata
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
- # Prepare provider data
126
- provider_data = {
127
- 'amount': float(request.amount),
128
- 'currency': request.currency,
129
- 'user_id': user.id,
130
- 'payment_id': str(payment.id),
131
- 'callback_url': request.callback_url,
132
- 'cancel_url': request.cancel_url,
133
- **request.metadata
134
- }
135
-
136
- # Process with provider
137
- provider_result = provider_instance.create_payment(provider_data)
138
-
139
- if provider_result.success:
140
- # Update payment with provider data
141
- payment.provider_payment_id = provider_result.provider_payment_id
142
- payment.save()
143
-
144
-
145
- return PaymentCreationResult(
146
- success=True,
147
- payment_id=str(payment.id),
148
- provider_payment_id=provider_result.provider_payment_id,
149
- payment_url=provider_result.payment_url
150
- )
151
- else:
152
- # Mark payment as failed
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
- return PaymentCreationResult(
158
- success=False,
159
- payment_id=str(payment.id),
160
- error=provider_result.error_message or 'Payment creation failed'
161
- )
162
-
163
- except ValidationError as e:
164
- logger.error(f"Payment validation error: {e}")
165
- return PaymentCreationResult(
166
- success=False,
167
- error=f"Invalid payment data: {e}"
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
- except Exception as e:
170
- logger.error(f"Payment creation failed: {e}", exc_info=True)
171
- return PaymentCreationResult(
172
- success=False,
173
- error=f"Internal error: {str(e)}"
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 process_webhook(
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
- Process payment webhook from provider.
171
+ Get payment status with optional provider check.
184
172
 
185
173
  Args:
186
- provider: Payment provider name
187
- webhook_data: Webhook payload data
188
- request_headers: HTTP headers for validation
174
+ request: Payment status request
189
175
 
190
176
  Returns:
191
- WebhookProcessingResult with processing status
177
+ PaymentResult: Current payment status
192
178
  """
193
179
  try:
194
- # Get provider instance
195
- provider_instance = self.provider_registry.get_provider(provider)
196
- if not provider_instance:
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
- # Process webhook with provider
203
- webhook_result = provider_instance.process_webhook(webhook_data)
204
- if not webhook_result.success:
205
- return WebhookProcessingResult(
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
- # Find payment by provider payment ID
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 WebhookProcessingResult(
193
+ return PaymentResult(
217
194
  success=False,
218
- error=f"Payment not found: {webhook_result.provider_payment_id}"
195
+ message=f"Payment {request.payment_id} not found",
196
+ error_code="payment_not_found"
219
197
  )
220
198
 
221
- # Process payment status update
222
- old_status = payment.status
223
- new_status = webhook_result.status
224
-
225
- with transaction.atomic():
226
- # Update payment
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
- Returns:
259
- Payment status information or None if not found
260
- """
261
- try:
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
- # Get from database
264
- payment = UniversalPayment.objects.get(id=payment_id)
214
+ # Convert to PaymentData
215
+ payment_data = PaymentData.model_validate(payment)
265
216
 
266
- return PaymentStatusResult(
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
- created_at=payment.created_at,
274
- updated_at=payment.updated_at
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
- logger.error(f"Error getting payment status {payment_id}: {e}")
281
- return None
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 get_user_payments(
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
- Get user's payment history.
241
+ Cancel payment if possible.
292
242
 
293
243
  Args:
294
- user: User object
295
- status: Filter by payment status
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
- List of PaymentHistoryItem objects
248
+ PaymentResult: Cancellation result
301
249
  """
302
250
  try:
303
- queryset = UniversalPayment.objects.filter(user=user)
251
+ self.logger.info("Cancelling payment", extra={
252
+ 'payment_id': payment_id,
253
+ 'reason': reason
254
+ })
304
255
 
305
- if status:
306
- queryset = queryset.filter(status=status)
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
- payments = queryset.order_by('-created_at')[offset:offset+limit]
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
- return [
311
- PaymentHistoryItem(
312
- id=str(payment.id),
313
- user_id=payment.user.id,
314
- amount=payment.pay_amount if payment.pay_amount else payment.amount_usd,
315
- currency=payment.currency_code,
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
- provider=payment.provider.name if payment.provider else 'unknown',
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
- for payment in payments
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
- logger.error(f"Error getting user payments for {user.id}: {e}")
328
- return []
306
+ return PaymentResult(**self._handle_exception(
307
+ "cancel_payment", e,
308
+ payment_id=payment_id
309
+ ).model_dump())
329
310
 
330
- def _process_payment_completion(self, payment: UniversalPayment) -> bool:
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
- balance_service = BalanceService()
343
- result = balance_service.add_funds(
344
- user=payment.user,
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 result.success
359
-
360
- except Exception as e:
361
- logger.error(f"Error processing payment completion {payment.id}: {e}")
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 _convert_to_usd(self, amount: Decimal, currency: str) -> Decimal:
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
- converted_amount = convert_currency(
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
- logger.info(f"Currency conversion: {amount} {currency} = {converted_amount} USD")
387
- return Decimal(str(converted_amount))
388
-
389
- except CurrencyError as e:
390
- logger.error(f"Currency conversion failed for {amount} {currency} to USD: {e}")
391
- # Fallback to 1:1 rate if conversion fails
392
- logger.warning(f"Using 1:1 fallback rate for {currency} to USD")
393
- return amount
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
- logger.error(f"Unexpected error in currency conversion: {e}")
397
- # Fallback to 1:1 rate for any other errors
398
- logger.warning(f"Using 1:1 fallback rate for {currency} to USD due to error")
399
- return amount
356
+ return self._create_error_result(
357
+ f"Currency conversion failed: {e}",
358
+ "conversion_failed"
359
+ )
400
360
 
401
- def process_webhook(self, provider: str, webhook_data: dict, headers: dict = None) -> 'WebhookProcessingResult':
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
- # Get provider instance for validation
415
- provider_instance = self.provider_registry.get_provider(provider)
416
- if not provider_instance:
417
- return WebhookProcessingResult(
418
- success=False,
419
- error_message=f"Unknown provider: {provider}"
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
- logger.error(f"Error processing webhook from {provider}: {e}")
489
- return WebhookProcessingResult(
490
- success=False,
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 _create_payment_event(self, payment: UniversalPayment, event_type: str, data: dict):
495
- """
496
- Create payment event for audit trail.
497
-
498
- Args:
499
- payment: Payment object
500
- event_type: Type of event
501
- data: Event data
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
- from ...models.events import PaymentEvent
386
+ queryset = UniversalPayment.objects.filter(user_id=user_id)
505
387
 
506
- # Get next sequence number
507
- last_event = PaymentEvent.objects.filter(
508
- payment_id=str(payment.id)
509
- ).order_by('-sequence_number').first()
388
+ if status:
389
+ queryset = queryset.filter(status=status)
510
390
 
511
- sequence_number = (last_event.sequence_number + 1) if last_event else 1
391
+ total_count = queryset.count()
392
+ payments = queryset.order_by('-created_at')[offset:offset + limit]
512
393
 
513
- PaymentEvent.objects.create(
514
- payment_id=str(payment.id),
515
- event_type=event_type,
516
- sequence_number=sequence_number,
517
- event_data=data,
518
- processed_by=f"payment_service_{timezone.now().timestamp()}",
519
- correlation_id=data.get('correlation_id'),
520
- idempotency_key=f"{payment.id}_{event_type}_{sequence_number}"
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
- logger.error(f"Failed to create payment event: {e}")
411
+ return self._handle_exception(
412
+ "get_user_payments", e,
413
+ user_id=user_id
414
+ )
525
415
 
526
- def get_payment_events(self, payment_id: str) -> List[dict]:
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 ...models.events import PaymentEvent
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
- events = PaymentEvent.objects.filter(
540
- payment_id=payment_id
541
- ).order_by('sequence_number')
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
- return [
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
- except Exception as e:
556
- logger.error(f"Error getting payment events for {payment_id}: {e}")
557
- return []
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
- for name, provider in self.provider_registry.get_all_providers().items()
576
- ]
450
+
451
+ except Exception as e:
452
+ return self._handle_exception("get_payment_stats", e)