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

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