django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) 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 -10
  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 +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  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 +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  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 +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  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 +172 -148
  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 -285
  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 +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  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 +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  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 +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  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 +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  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/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  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 -63
  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 -122
  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 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  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 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  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 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,573 +1,452 @@
1
1
  """
2
- Payment 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
- from typing import Optional, List
7
+ from typing import Optional, Dict, Any
9
8
  from decimal import Decimal
10
- from django.db import transaction
11
- from django.contrib.auth import get_user_model
9
+ from django.contrib.auth.models import User
12
10
  from django.utils import timezone
13
- from pydantic import BaseModel, Field, ValidationError
14
11
 
15
- from .balance_service import BalanceService
16
- from .fallback_service import get_fallback_service
17
- from ...models import UniversalPayment, UserBalance, Transaction
18
- from ...utils.config_utils import get_payments_config
19
- from ..providers.registry import ProviderRegistry
20
- from django_cfg.modules.django_logger import get_logger
21
- from ..monitoring.provider_health import get_health_monitor
22
- from ..internal_types import (
23
- ProviderResponse, WebhookData, ServiceOperationResult,
24
- BalanceUpdateRequest, AccessCheckRequest, AccessCheckResult,
25
- PaymentCreationResult, WebhookProcessingResult, PaymentStatusResult,
26
- PaymentHistoryItem, ProviderInfo
12
+ from .base import BaseService
13
+ from ..types import (
14
+ PaymentCreateRequest, PaymentStatusRequest, PaymentResult,
15
+ PaymentData, ServiceOperationResult
27
16
  )
17
+ from ...models import UniversalPayment, Currency, ProviderCurrency
18
+ from django_cfg.modules.django_currency import convert_currency, get_exchange_rate
19
+ # ConfigService removed - using direct Constance access
20
+ from ..providers import ProviderRegistry, get_provider_registry
28
21
 
29
- # Import django_currency module for currency conversion
30
- from django_cfg.modules.django_currency import convert_currency, CurrencyError
31
- from ...models.events import PaymentEvent
32
22
 
33
- 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
-
58
-
59
- class WebhookProcessingResult(BaseModel):
60
- """Type-safe webhook processing result"""
61
- success: bool
62
- payment_id: Optional[str] = None
63
- status_updated: bool = False
64
- balance_updated: bool = False
65
- error_message: Optional[str] = None
66
-
67
-
68
- class PaymentService:
23
+ class PaymentService(BaseService):
69
24
  """
70
- Universal payment processing service.
25
+ Payment service with business logic and validation.
71
26
 
72
- Handles payment creation, webhook processing, and provider management.
73
- Integrates with balance management and caching.
27
+ Handles payment operations using Pydantic validation and Django ORM managers.
74
28
  """
75
29
 
76
30
  def __init__(self):
77
- """Initialize payment service with dependencies"""
78
- self.provider_registry = ProviderRegistry()
79
- 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()
80
35
 
81
- def create_payment(self, payment_data: dict) -> 'PaymentCreationResult':
36
+ def create_payment(self, request: PaymentCreateRequest) -> PaymentResult:
82
37
  """
83
- Create a new payment with the specified provider.
38
+ Create new payment with full validation.
84
39
 
85
40
  Args:
86
- payment_data: Dictionary with payment details
41
+ request: Payment creation request with validation
87
42
 
88
43
  Returns:
89
- PaymentCreationResult with payment details or error information
44
+ PaymentResult: Result with payment data or error
90
45
  """
91
46
  try:
92
- # Validate payment request
93
- request = PaymentRequest(
94
- user_id=payment_data['user_id'],
95
- amount=payment_data['amount'],
96
- currency=payment_data.get('currency', 'USD'),
97
- provider=payment_data['provider'],
98
- metadata=payment_data.get('metadata', {})
99
- )
47
+ # Validate request
48
+ if isinstance(request, dict):
49
+ request = PaymentCreateRequest(**request)
100
50
 
101
- # Get provider instance
102
- provider_instance = self.provider_registry.get_provider(request.provider)
103
- if not provider_instance:
104
- 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(
105
63
  success=False,
106
- error=f"Payment provider '{request.provider}' is not available"
64
+ message=f"User {request.user_id} not found",
65
+ error_code="user_not_found"
107
66
  )
108
67
 
109
- # Get user
110
- 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
+ )
111
76
 
112
- # Convert currency if needed
113
- 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
+ )
114
85
 
115
- # Create payment record
116
- with transaction.atomic():
86
+ # Create payment in database first
87
+ def create_payment_transaction():
117
88
  payment = UniversalPayment.objects.create(
118
89
  user=user,
90
+ amount_usd=request.amount_usd,
91
+ currency_code=request.currency_code,
119
92
  provider=request.provider,
120
- amount_usd=amount_usd,
121
- currency_code=request.currency,
122
93
  status=UniversalPayment.PaymentStatus.PENDING,
123
- 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
124
99
  )
125
-
126
- # Prepare provider data
127
- provider_data = {
128
- 'amount': float(request.amount),
129
- 'currency': request.currency,
130
- 'user_id': user.id,
131
- 'payment_id': str(payment.id),
132
- 'callback_url': request.callback_url,
133
- 'cancel_url': request.cancel_url,
134
- **request.metadata
135
- }
136
-
137
- # Process with provider
138
- provider_result = provider_instance.create_payment(provider_data)
139
-
140
- if provider_result.success:
141
- # Update payment with provider data
142
- payment.provider_payment_id = provider_result.provider_payment_id
143
- payment.save()
144
-
145
-
146
- return PaymentCreationResult(
147
- success=True,
148
- payment_id=str(payment.id),
149
- provider_payment_id=provider_result.provider_payment_id,
150
- payment_url=provider_result.payment_url
151
- )
152
- else:
153
- # Mark payment as failed
154
- payment.status = UniversalPayment.PaymentStatus.FAILED
155
- payment.error_message = provider_result.error_message or 'Unknown provider error'
100
+ return payment
101
+
102
+ payment = self._execute_with_transaction(create_payment_transaction)
103
+
104
+ # Create payment with provider
105
+ from ..providers.base import PaymentRequest as ProviderPaymentRequest
106
+
107
+ provider_request = ProviderPaymentRequest(
108
+ amount_usd=request.amount_usd,
109
+ currency_code=request.currency_code,
110
+ order_id=str(payment.id),
111
+ callback_url=request.callback_url,
112
+ cancel_url=request.cancel_url,
113
+ description=request.description,
114
+ metadata=request.metadata
115
+ )
116
+
117
+ provider_response = provider.create_payment(provider_request)
118
+
119
+ # Update payment with provider response
120
+ if provider_response.success:
121
+ def update_payment_transaction():
122
+ payment.provider_payment_id = provider_response.provider_payment_id
123
+ payment.crypto_amount = provider_response.amount
124
+ payment.payment_url = provider_response.payment_url
125
+ payment.qr_code_url = provider_response.qr_code_url
126
+ payment.wallet_address = provider_response.wallet_address
127
+ if provider_response.expires_at:
128
+ payment.expires_at = provider_response.expires_at
156
129
  payment.save()
157
-
158
- return PaymentCreationResult(
159
- success=False,
160
- payment_id=str(payment.id),
161
- error=provider_result.error_message or 'Payment creation failed'
162
- )
163
-
164
- except ValidationError as e:
165
- logger.error(f"Payment validation error: {e}")
166
- return PaymentCreationResult(
167
- success=False,
168
- 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
169
149
  )
170
- except Exception as e:
171
- logger.error(f"Payment creation failed: {e}", exc_info=True)
172
- return PaymentCreationResult(
173
- success=False,
174
- 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()}
175
161
  )
162
+
163
+ except Exception as e:
164
+ return PaymentResult(**self._handle_exception(
165
+ "create_payment", e,
166
+ user_id=request.user_id if hasattr(request, 'user_id') else None
167
+ ).model_dump())
176
168
 
177
- def process_webhook(
178
- self,
179
- provider: str,
180
- webhook_data: dict,
181
- request_headers: Optional[dict] = None
182
- ) -> 'WebhookProcessingResult':
169
+ def get_payment_status(self, request: PaymentStatusRequest) -> PaymentResult:
183
170
  """
184
- Process payment webhook from provider.
171
+ Get payment status with optional provider check.
185
172
 
186
173
  Args:
187
- provider: Payment provider name
188
- webhook_data: Webhook payload data
189
- request_headers: HTTP headers for validation
174
+ request: Payment status request
190
175
 
191
176
  Returns:
192
- WebhookProcessingResult with processing status
177
+ PaymentResult: Current payment status
193
178
  """
194
179
  try:
195
- # Get provider instance
196
- provider_instance = self.provider_registry.get_provider(provider)
197
- if not provider_instance:
198
- return WebhookProcessingResult(
199
- success=False,
200
- error=f"Provider '{provider}' not found"
201
- )
180
+ # Validate request
181
+ if isinstance(request, dict):
182
+ request = PaymentStatusRequest(**request)
202
183
 
203
- # Process webhook with provider
204
- webhook_result = provider_instance.process_webhook(webhook_data)
205
- if not webhook_result.success:
206
- return WebhookProcessingResult(
207
- success=False,
208
- error=webhook_result.error_message or "Webhook processing failed"
209
- )
184
+ self.logger.debug("Getting payment status", extra={
185
+ 'payment_id': request.payment_id,
186
+ 'force_provider_check': request.force_provider_check
187
+ })
210
188
 
211
- # Find payment by provider payment ID
189
+ # Get payment
212
190
  try:
213
- payment = UniversalPayment.objects.get(
214
- provider_payment_id=webhook_result.provider_payment_id
215
- )
191
+ payment = UniversalPayment.objects.get(id=request.payment_id)
216
192
  except UniversalPayment.DoesNotExist:
217
- return WebhookProcessingResult(
193
+ return PaymentResult(
218
194
  success=False,
219
- 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"
220
197
  )
221
198
 
222
- # Process payment status update
223
- old_status = payment.status
224
- new_status = webhook_result.status
225
-
226
- with transaction.atomic():
227
- # Update payment
228
- payment.status = new_status
229
- payment.save()
230
-
231
- # Process completion if status changed to completed
232
- balance_updated = False
233
- if (new_status == UniversalPayment.PaymentStatus.COMPLETED and
234
- old_status != UniversalPayment.PaymentStatus.COMPLETED):
235
- balance_updated = self._process_payment_completion(payment)
236
-
237
-
238
- return WebhookProcessingResult(
239
- success=True,
240
- payment_id=str(payment.id),
241
- status_updated=(old_status != new_status),
242
- balance_updated=balance_updated
199
+ # Check user authorization if provided
200
+ if request.user_id and payment.user_id != request.user_id:
201
+ return PaymentResult(
202
+ success=False,
203
+ message="Access denied to payment",
204
+ error_code="access_denied"
243
205
  )
244
-
245
- except Exception as e:
246
- logger.error(f"Webhook processing failed for {provider}: {e}", exc_info=True)
247
- return WebhookProcessingResult(
248
- success=False,
249
- error=f"Webhook processing error: {str(e)}"
250
- )
251
-
252
- def get_payment_status(self, payment_id: str) -> Optional['PaymentStatusResult']:
253
- """
254
- Get payment status by ID.
255
-
256
- Args:
257
- payment_id: Payment UUID
258
206
 
259
- Returns:
260
- Payment status information or None if not found
261
- """
262
- 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()
263
213
 
264
- # Get from database
265
- payment = UniversalPayment.objects.get(id=payment_id)
214
+ # Convert to PaymentData
215
+ payment_data = PaymentData.model_validate(payment)
266
216
 
267
- return PaymentStatusResult(
217
+ return PaymentResult(
218
+ success=True,
219
+ message="Payment status retrieved",
268
220
  payment_id=str(payment.id),
269
221
  status=payment.status,
270
222
  amount_usd=payment.amount_usd,
223
+ crypto_amount=payment.crypto_amount,
271
224
  currency_code=payment.currency_code,
272
- provider=payment.provider,
273
225
  provider_payment_id=payment.provider_payment_id,
274
- created_at=payment.created_at,
275
- 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()}
276
231
  )
277
232
 
278
- except UniversalPayment.DoesNotExist:
279
- return None
280
233
  except Exception as e:
281
- logger.error(f"Error getting payment status {payment_id}: {e}")
282
- 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())
283
238
 
284
- def get_user_payments(
285
- self,
286
- user: User,
287
- status: Optional[str] = None,
288
- limit: int = 50,
289
- offset: int = 0
290
- ) -> List[PaymentHistoryItem]:
239
+ def cancel_payment(self, payment_id: str, reason: str = None) -> PaymentResult:
291
240
  """
292
- Get user's payment history.
241
+ Cancel payment if possible.
293
242
 
294
243
  Args:
295
- user: User object
296
- status: Filter by payment status
297
- limit: Number of payments to return
298
- offset: Pagination offset
244
+ payment_id: Payment ID to cancel
245
+ reason: Cancellation reason
299
246
 
300
247
  Returns:
301
- List of PaymentHistoryItem objects
248
+ PaymentResult: Cancellation result
302
249
  """
303
250
  try:
304
- queryset = UniversalPayment.objects.filter(user=user)
251
+ self.logger.info("Cancelling payment", extra={
252
+ 'payment_id': payment_id,
253
+ 'reason': reason
254
+ })
305
255
 
306
- if status:
307
- 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
+ )
265
+
266
+ # Check if payment can be cancelled
267
+ if not payment.can_be_cancelled():
268
+ return PaymentResult(
269
+ success=False,
270
+ message=f"Payment {payment_id} cannot be cancelled (status: {payment.status})",
271
+ error_code="cannot_cancel"
272
+ )
273
+
274
+ # Cancel using manager
275
+ def cancel_payment_transaction():
276
+ return payment.cancel(reason)
308
277
 
309
- payments = queryset.order_by('-created_at')[offset:offset+limit]
278
+ success = self._execute_with_transaction(cancel_payment_transaction)
310
279
 
311
- return [
312
- PaymentHistoryItem(
313
- id=str(payment.id),
314
- user_id=payment.user.id,
315
- amount=payment.pay_amount if payment.pay_amount else payment.amount_usd,
316
- currency=payment.currency_code,
280
+ if success:
281
+ payment.refresh_from_db()
282
+ payment_data = PaymentData.model_validate(payment)
283
+
284
+ self._log_operation(
285
+ "cancel_payment",
286
+ True,
287
+ payment_id=payment_id,
288
+ reason=reason
289
+ )
290
+
291
+ return PaymentResult(
292
+ success=True,
293
+ message="Payment cancelled successfully",
294
+ payment_id=str(payment.id),
317
295
  status=payment.status,
318
- provider=payment.provider.name if payment.provider else 'unknown',
319
- provider_payment_id=payment.provider_payment_id,
320
- created_at=payment.created_at,
321
- updated_at=payment.updated_at,
322
- metadata=payment.metadata or {}
296
+ data={'payment': payment_data.model_dump()}
323
297
  )
324
- for payment in payments
325
- ]
326
-
298
+ else:
299
+ return PaymentResult(
300
+ success=False,
301
+ message="Failed to cancel payment",
302
+ error_code="cancel_failed"
303
+ )
304
+
327
305
  except Exception as e:
328
- logger.error(f"Error getting user payments for {user.id}: {e}")
329
- return []
306
+ return PaymentResult(**self._handle_exception(
307
+ "cancel_payment", e,
308
+ payment_id=payment_id
309
+ ).model_dump())
330
310
 
331
- def _process_payment_completion(self, payment: UniversalPayment) -> bool:
332
- """
333
- Process completed payment by adding funds to user balance.
334
-
335
- Args:
336
- payment: Completed payment object
337
-
338
- Returns:
339
- True if balance was updated, False otherwise
340
- """
311
+ def _validate_currency(self, currency_code: str) -> ServiceOperationResult:
312
+ """Validate currency is supported."""
341
313
  try:
314
+ currency = Currency.objects.get(code=currency_code, is_enabled=True)
315
+
316
+ # Check if currency is supported by any provider
317
+ provider_currency = ProviderCurrency.objects.filter(
318
+ currency=currency,
319
+ is_enabled=True
320
+ ).first()
321
+
322
+ if not provider_currency:
323
+ return self._create_error_result(
324
+ f"Currency {currency_code} not supported by any provider",
325
+ "currency_not_supported"
326
+ )
342
327
 
343
- balance_service = BalanceService()
344
- result = balance_service.add_funds(
345
- user=payment.user,
346
- amount=payment.amount_usd,
347
- currency_code='USD',
348
- source='payment',
349
- reference_id=str(payment.id),
350
- metadata={
351
- 'provider': payment.provider.name if payment.provider else 'unknown',
352
- 'provider_payment_id': payment.provider_payment_id,
353
- 'pay_amount': str(payment.pay_amount) if payment.pay_amount else str(payment.amount_usd),
354
- 'currency_code': payment.currency_code
355
- }
328
+ return self._create_success_result(
329
+ "Currency is valid",
330
+ {'currency': currency_code}
356
331
  )
357
332
 
358
-
359
- return result.success
360
-
361
- except Exception as e:
362
- logger.error(f"Error processing payment completion {payment.id}: {e}")
363
- return False
333
+ except Currency.DoesNotExist:
334
+ return self._create_error_result(
335
+ f"Currency {currency_code} not found",
336
+ "currency_not_found"
337
+ )
364
338
 
365
- def _convert_to_usd(self, amount: Decimal, currency: str) -> Decimal:
366
- """
367
- Convert amount to USD using django_currency module.
368
-
369
- Args:
370
- amount: Amount to convert
371
- currency: Source currency
372
-
373
- Returns:
374
- Amount in USD
375
- """
376
- if currency == 'USD':
377
- return amount
378
-
339
+ def _convert_usd_to_crypto(self, amount_usd: float, currency_code: str) -> ServiceOperationResult:
340
+ """Convert USD amount to cryptocurrency."""
379
341
  try:
380
342
  # Use django_currency module for conversion
381
- converted_amount = convert_currency(
382
- amount=float(amount),
383
- from_currency=currency,
384
- to_currency='USD'
385
- )
386
-
387
- logger.info(f"Currency conversion: {amount} {currency} = {converted_amount} USD")
388
- return Decimal(str(converted_amount))
343
+ crypto_amount = convert_currency(amount_usd, 'USD', currency_code)
389
344
 
390
- except CurrencyError as e:
391
- logger.error(f"Currency conversion failed for {amount} {currency} to USD: {e}")
392
- # Fallback to 1:1 rate if conversion fails
393
- logger.warning(f"Using 1:1 fallback rate for {currency} to USD")
394
- 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
+ )
395
354
 
396
355
  except Exception as e:
397
- logger.error(f"Unexpected error in currency conversion: {e}")
398
- # Fallback to 1:1 rate for any other errors
399
- logger.warning(f"Using 1:1 fallback rate for {currency} to USD due to error")
400
- return amount
356
+ return self._create_error_result(
357
+ f"Currency conversion failed: {e}",
358
+ "conversion_failed"
359
+ )
401
360
 
402
- def process_webhook(self, provider: str, webhook_data: dict, headers: dict = None) -> 'WebhookProcessingResult':
403
- """
404
- Process webhook from payment provider.
405
-
406
- Args:
407
- provider: Provider name
408
- webhook_data: Webhook payload
409
- headers: Request headers for validation
410
-
411
- Returns:
412
- WebhookProcessingResult with processing status
413
- """
361
+ def _check_provider_status(self, payment: UniversalPayment) -> ServiceOperationResult:
362
+ """Check payment status with provider."""
414
363
  try:
415
- # Get provider instance for validation
416
- provider_instance = self.provider_registry.get_provider(provider)
417
- if not provider_instance:
418
- return WebhookProcessingResult(
419
- success=False,
420
- error_message=f"Unknown provider: {provider}"
421
- )
422
-
423
- # Validate webhook
424
- if hasattr(provider_instance, 'validate_webhook'):
425
- is_valid = provider_instance.validate_webhook(webhook_data, headers)
426
- if not is_valid:
427
- logger.warning(f"Invalid webhook from {provider}: {webhook_data}")
428
- return WebhookProcessingResult(
429
- success=False,
430
- error_message="Webhook validation failed"
431
- )
432
-
433
- # Process webhook data
434
- processed_data = provider_instance.process_webhook(webhook_data)
435
-
436
- # Find payment record
437
- payment_id = processed_data.payment_id
438
- if not payment_id:
439
- return WebhookProcessingResult(
440
- success=False,
441
- error_message="No payment ID found in webhook"
442
- )
364
+ # This would integrate with provider services
365
+ # For now, return success without changes
366
+ return self._create_success_result(
367
+ "Provider status checked",
368
+ {'status_changed': False}
369
+ )
443
370
 
444
- # Update payment
445
- with transaction.atomic():
446
- try:
447
- payment = UniversalPayment.objects.get(
448
- provider_payment_id=payment_id,
449
- provider=provider
450
- )
451
-
452
- # Update payment status and data
453
- old_status = payment.status
454
- payment.update_from_webhook(webhook_data)
455
-
456
- # Create event for audit trail
457
- self._create_payment_event(
458
- payment=payment,
459
- event_type='webhook_processed',
460
- data={
461
- 'provider': provider,
462
- 'old_status': old_status,
463
- 'new_status': payment.status,
464
- 'webhook_data': webhook_data
465
- }
466
- )
467
-
468
- # Process completion if needed
469
- if payment.is_completed and old_status != payment.status:
470
- success = self._process_payment_completion(payment)
471
- if success:
472
- payment.processed_at = timezone.now()
473
- payment.save()
474
-
475
- return WebhookProcessingResult(
476
- success=True,
477
- payment_id=str(payment.id),
478
- new_status=payment.status
479
- )
480
-
481
- except UniversalPayment.DoesNotExist:
482
- logger.error(f"Payment not found for webhook: provider={provider}, payment_id={payment_id}")
483
- return WebhookProcessingResult(
484
- success=False,
485
- error_message="Payment not found"
486
- )
487
-
488
371
  except Exception as e:
489
- logger.error(f"Error processing webhook from {provider}: {e}")
490
- return WebhookProcessingResult(
491
- success=False,
492
- error_message=str(e)
372
+ return self._create_error_result(
373
+ f"Provider check failed: {e}",
374
+ "provider_check_failed"
493
375
  )
494
376
 
495
- def _create_payment_event(self, payment: UniversalPayment, event_type: str, data: dict):
496
- """
497
- Create payment event for audit trail.
498
-
499
- Args:
500
- payment: Payment object
501
- event_type: Type of event
502
- data: Event data
503
- """
377
+ def get_user_payments(
378
+ self,
379
+ user_id: int,
380
+ status: Optional[str] = None,
381
+ limit: int = 50,
382
+ offset: int = 0
383
+ ) -> ServiceOperationResult:
384
+ """Get user payments with pagination."""
504
385
  try:
505
- # Get next sequence number
506
- last_event = PaymentEvent.objects.filter(
507
- payment_id=str(payment.id)
508
- ).order_by('-sequence_number').first()
386
+ queryset = UniversalPayment.objects.filter(user_id=user_id)
509
387
 
510
- sequence_number = (last_event.sequence_number + 1) if last_event else 1
511
-
512
- PaymentEvent.objects.create(
513
- payment_id=str(payment.id),
514
- event_type=event_type,
515
- sequence_number=sequence_number,
516
- event_data=data,
517
- processed_by=f"payment_service_{timezone.now().timestamp()}",
518
- correlation_id=data.get('correlation_id'),
519
- idempotency_key=f"{payment.id}_{event_type}_{sequence_number}"
520
- )
388
+ if status:
389
+ queryset = queryset.filter(status=status)
521
390
 
522
- except Exception as e:
523
- logger.error(f"Failed to create payment event: {e}")
524
-
525
- def get_payment_events(self, payment_id: str) -> List[dict]:
526
- """
527
- Get all events for a payment.
528
-
529
- Args:
530
- payment_id: Payment ID
391
+ total_count = queryset.count()
392
+ payments = queryset.order_by('-created_at')[offset:offset + limit]
531
393
 
532
- Returns:
533
- List of payment events
534
- """
535
- try:
536
- events = PaymentEvent.objects.filter(
537
- payment_id=payment_id
538
- ).order_by('sequence_number')
394
+ payment_data = [
395
+ PaymentData.model_validate(payment).model_dump()
396
+ for payment in payments
397
+ ]
539
398
 
540
- return [
399
+ return self._create_success_result(
400
+ f"Retrieved {len(payment_data)} payments",
541
401
  {
542
- 'id': str(event.id),
543
- 'event_type': event.event_type,
544
- 'sequence_number': event.sequence_number,
545
- 'event_data': event.event_data,
546
- 'created_at': event.created_at,
547
- 'processed_by': event.processed_by
402
+ 'payments': payment_data,
403
+ 'total_count': total_count,
404
+ 'limit': limit,
405
+ 'offset': offset,
406
+ 'has_more': offset + limit < total_count
548
407
  }
549
- for event in events
550
- ]
408
+ )
551
409
 
552
410
  except Exception as e:
553
- logger.error(f"Error getting payment events for {payment_id}: {e}")
554
- return []
555
-
411
+ return self._handle_exception(
412
+ "get_user_payments", e,
413
+ user_id=user_id
414
+ )
556
415
 
557
- def list_available_providers(self) -> List[ProviderInfo]:
558
- """
559
- List all available payment providers.
560
-
561
- Returns:
562
- List of ProviderInfo objects
563
- """
564
- return [
565
- ProviderInfo(
566
- name=name,
567
- display_name=provider.get_display_name(),
568
- supported_currencies=provider.get_supported_currencies(),
569
- is_active=provider.is_active(),
570
- features={'provider_type': provider.get_provider_type()}
416
+ def get_payment_stats(self, days: int = 30) -> ServiceOperationResult:
417
+ """Get payment statistics."""
418
+ try:
419
+ from datetime import timedelta
420
+
421
+ since = timezone.now() - timedelta(days=days)
422
+
423
+ stats = UniversalPayment.objects.filter(
424
+ created_at__gte=since
425
+ ).aggregate(
426
+ total_payments=models.Count('id'),
427
+ total_amount_usd=models.Sum('amount_usd'),
428
+ completed_payments=models.Count(
429
+ 'id',
430
+ filter=models.Q(status=UniversalPayment.PaymentStatus.COMPLETED)
431
+ ),
432
+ failed_payments=models.Count(
433
+ 'id',
434
+ filter=models.Q(status=UniversalPayment.PaymentStatus.FAILED)
435
+ )
436
+ )
437
+
438
+ # Calculate success rate
439
+ total = stats['total_payments'] or 0
440
+ completed = stats['completed_payments'] or 0
441
+ success_rate = (completed / total * 100) if total > 0 else 0
442
+
443
+ stats['success_rate'] = round(success_rate, 2)
444
+ stats['period_days'] = days
445
+
446
+ return self._create_success_result(
447
+ f"Payment statistics for {days} days",
448
+ stats
571
449
  )
572
- for name, provider in self.provider_registry.get_all_providers().items()
573
- ]
450
+
451
+ except Exception as e:
452
+ return self._handle_exception("get_payment_stats", e)