django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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 (246) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +269 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +183 -460
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  48. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  49. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  50. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  51. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  52. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  53. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  54. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
  55. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  56. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  57. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
  58. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  59. django_cfg/apps/payments/config/__init__.py +14 -15
  60. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  61. django_cfg/apps/payments/config/helpers.py +8 -13
  62. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  63. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  64. django_cfg/apps/payments/middleware/api_access.py +32 -6
  65. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  66. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  67. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  68. django_cfg/apps/payments/models/balance.py +12 -0
  69. django_cfg/apps/payments/models/currencies.py +106 -32
  70. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  71. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  72. django_cfg/apps/payments/models/payments.py +94 -0
  73. django_cfg/apps/payments/services/core/base.py +4 -4
  74. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  75. django_cfg/apps/payments/services/core/payment_service.py +266 -39
  76. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  77. django_cfg/apps/payments/services/providers/base.py +303 -41
  78. django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
  79. django_cfg/apps/payments/services/providers/models/base.py +145 -0
  80. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  81. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  82. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  83. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  84. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  85. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  86. django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
  87. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  88. django_cfg/apps/payments/services/providers/registry.py +9 -37
  89. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  90. django_cfg/apps/payments/services/types/requests.py +19 -7
  91. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  92. django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
  93. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  94. django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
  95. django_cfg/apps/payments/tasks/__init__.py +39 -0
  96. django_cfg/apps/payments/tasks/types.py +73 -0
  97. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  98. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  99. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  100. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  101. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  102. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  103. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  104. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  105. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  106. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  107. django_cfg/apps/payments/urls.py +3 -2
  108. django_cfg/apps/payments/urls_admin.py +1 -1
  109. django_cfg/apps/payments/views/api/currencies.py +8 -5
  110. django_cfg/apps/payments/views/overview/services.py +2 -2
  111. django_cfg/apps/payments/views/serializers/currencies.py +22 -8
  112. django_cfg/apps/support/admin/__init__.py +10 -1
  113. django_cfg/apps/support/admin/support_admin.py +338 -141
  114. django_cfg/apps/tasks/admin/__init__.py +11 -0
  115. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  116. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  117. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  118. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  119. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  120. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  121. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  122. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  123. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  124. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  125. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  126. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  127. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  128. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  129. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  130. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  131. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  132. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  133. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  134. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  135. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  136. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  137. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  138. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  139. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  140. django_cfg/apps/tasks/urls.py +2 -2
  141. django_cfg/apps/tasks/urls_admin.py +2 -2
  142. django_cfg/apps/tasks/utils/__init__.py +1 -0
  143. django_cfg/apps/tasks/utils/simulator.py +356 -0
  144. django_cfg/apps/tasks/views/__init__.py +16 -0
  145. django_cfg/apps/tasks/views/api.py +569 -0
  146. django_cfg/apps/tasks/views/dashboard.py +58 -0
  147. django_cfg/config.py +1 -1
  148. django_cfg/core/config.py +10 -5
  149. django_cfg/core/generation.py +1 -1
  150. django_cfg/core/integration/__init__.py +21 -0
  151. django_cfg/management/commands/__init__.py +13 -1
  152. django_cfg/management/commands/migrate_all.py +9 -3
  153. django_cfg/management/commands/migrator.py +11 -6
  154. django_cfg/management/commands/rundramatiq.py +3 -2
  155. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  156. django_cfg/middleware/__init__.py +0 -2
  157. django_cfg/models/api_keys.py +115 -0
  158. django_cfg/models/constance.py +0 -11
  159. django_cfg/models/payments.py +137 -3
  160. django_cfg/modules/django_admin/__init__.py +64 -0
  161. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  162. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  163. django_cfg/modules/django_admin/decorators/display.py +106 -0
  164. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  165. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  166. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  167. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  168. django_cfg/modules/django_admin/models/__init__.py +20 -0
  169. django_cfg/modules/django_admin/models/action_models.py +33 -0
  170. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  171. django_cfg/modules/django_admin/models/base.py +26 -0
  172. django_cfg/modules/django_admin/models/display_models.py +31 -0
  173. django_cfg/modules/django_admin/utils/badges.py +159 -0
  174. django_cfg/modules/django_admin/utils/displays.py +247 -0
  175. django_cfg/modules/django_currency/__init__.py +2 -2
  176. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  177. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  178. django_cfg/modules/django_currency/core/converter.py +12 -12
  179. django_cfg/modules/django_currency/database/__init__.py +2 -2
  180. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  181. django_cfg/modules/django_llm/llm/client.py +10 -2
  182. django_cfg/modules/django_tasks.py +54 -21
  183. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  184. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  185. django_cfg/modules/django_unfold/dashboard.py +14 -13
  186. django_cfg/modules/django_unfold/models/config.py +1 -1
  187. django_cfg/registry/core.py +7 -9
  188. django_cfg/registry/third_party.py +2 -2
  189. django_cfg/template_archive/django_sample.zip +0 -0
  190. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
  191. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
  192. django_cfg/apps/accounts/admin/activity.py +0 -96
  193. django_cfg/apps/accounts/admin/group.py +0 -17
  194. django_cfg/apps/accounts/admin/otp.py +0 -59
  195. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  196. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  197. django_cfg/apps/accounts/admin/user.py +0 -300
  198. django_cfg/apps/agents/core/agent.py +0 -281
  199. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  200. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  201. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  202. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  203. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  204. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  205. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  206. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  207. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  208. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  209. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  210. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  211. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  212. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  213. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  214. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  215. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  216. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  217. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  218. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  219. django_cfg/apps/payments/config/constance/fields.py +0 -69
  220. django_cfg/apps/payments/config/constance/settings.py +0 -160
  221. django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
  222. django_cfg/apps/tasks/admin.py +0 -320
  223. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  224. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  225. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  226. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  227. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  228. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  229. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  230. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  231. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  232. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  233. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  234. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  235. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  236. django_cfg/apps/tasks/views.py +0 -461
  237. django_cfg/management/commands/auto_generate.py +0 -486
  238. django_cfg/middleware/static_nocache.py +0 -55
  239. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  240. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  241. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  242. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  243. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  244. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -4,13 +4,44 @@ Payment managers for the Universal Payment System v2.0.
4
4
  Optimized querysets and managers for payment operations.
5
5
  """
6
6
 
7
+ from typing import Optional, Dict, Any
8
+ from decimal import Decimal
9
+ from datetime import datetime
7
10
  from django.db import models
8
11
  from django.utils import timezone
12
+ from pydantic import BaseModel, Field, ConfigDict
9
13
  from django_cfg.modules.django_logger import get_logger
10
14
 
11
15
  logger = get_logger("payment_managers")
12
16
 
13
17
 
18
+ class PaymentStatusUpdateFields(BaseModel):
19
+ """
20
+ Typed model for extra fields when updating payment status.
21
+
22
+ Ensures type safety and validation for payment status updates.
23
+ """
24
+ model_config = ConfigDict(
25
+ validate_assignment=True,
26
+ extra="forbid",
27
+ str_strip_whitespace=True
28
+ )
29
+
30
+ # Transaction related fields
31
+ transaction_hash: Optional[str] = Field(None, min_length=1, max_length=200, description="Blockchain transaction hash")
32
+ confirmations_count: Optional[int] = Field(None, ge=0, description="Number of blockchain confirmations")
33
+
34
+ # Amount related fields
35
+ actual_amount_usd: Optional[Decimal] = Field(None, gt=0, description="Actual amount received in USD")
36
+ fee_amount_usd: Optional[Decimal] = Field(None, ge=0, description="Fee amount in USD")
37
+
38
+ # Provider data
39
+ provider_data: Optional[Dict[str, Any]] = Field(None, description="Provider-specific data")
40
+
41
+ # Completion timestamp (auto-set by manager for completed status)
42
+ completed_at: Optional[datetime] = Field(None, description="Completion timestamp")
43
+
44
+
14
45
  class PaymentQuerySet(models.QuerySet):
15
46
  """
16
47
  Optimized queryset for payment operations.
@@ -392,17 +423,15 @@ class PaymentManager(models.Manager):
392
423
  })
393
424
  return False
394
425
 
395
- # Update payment
396
- payment.status = 'completed'
397
- payment.completed_at = timezone.now()
398
- if actual_amount_usd:
399
- payment.actual_amount_usd = actual_amount_usd
400
- if transaction_hash:
401
- payment.transaction_hash = transaction_hash
426
+ # Update payment using centralized status update method
427
+ extra_fields = PaymentStatusUpdateFields(
428
+ actual_amount_usd=actual_amount_usd,
429
+ transaction_hash=transaction_hash
430
+ )
402
431
 
403
- payment.save(update_fields=[
404
- 'status', 'completed_at', 'actual_amount_usd', 'transaction_hash', 'updated_at'
405
- ])
432
+ success = self.update_payment_status(payment, 'completed', extra_fields)
433
+ if not success:
434
+ return False
406
435
 
407
436
  logger.info(f"Payment marked as completed", extra={
408
437
  'payment_id': str(payment.id),
@@ -438,18 +467,25 @@ class PaymentManager(models.Manager):
438
467
  else:
439
468
  payment = payment_id
440
469
 
441
- # Update payment
442
- payment.status = 'failed'
470
+ # Prepare error info
471
+ provider_data = payment.provider_data.copy() if payment.provider_data else {}
443
472
  if reason or error_code:
444
- if 'error_info' not in payment.provider_data:
445
- payment.provider_data['error_info'] = {}
473
+ if 'error_info' not in provider_data:
474
+ provider_data['error_info'] = {}
446
475
  if reason:
447
- payment.provider_data['error_info']['reason'] = reason
476
+ provider_data['error_info']['reason'] = reason
448
477
  if error_code:
449
- payment.provider_data['error_info']['code'] = error_code
450
- payment.provider_data['error_info']['failed_at'] = timezone.now().isoformat()
478
+ provider_data['error_info']['code'] = error_code
479
+ provider_data['error_info']['failed_at'] = timezone.now().isoformat()
451
480
 
452
- payment.save(update_fields=['status', 'provider_data', 'updated_at'])
481
+ # Update payment using centralized status update method
482
+ extra_fields = PaymentStatusUpdateFields(
483
+ provider_data=provider_data if provider_data != payment.provider_data else None
484
+ )
485
+
486
+ success = self.update_payment_status(payment, 'failed', extra_fields)
487
+ if not success:
488
+ return False
453
489
 
454
490
  logger.warning(f"Payment marked as failed", extra={
455
491
  'payment_id': str(payment.id),
@@ -491,15 +527,22 @@ class PaymentManager(models.Manager):
491
527
  })
492
528
  return False
493
529
 
494
- # Update payment
495
- payment.status = 'cancelled'
530
+ # Prepare cancellation info
531
+ provider_data = payment.provider_data.copy() if payment.provider_data else {}
496
532
  if reason:
497
- if 'cancellation_info' not in payment.provider_data:
498
- payment.provider_data['cancellation_info'] = {}
499
- payment.provider_data['cancellation_info']['reason'] = reason
500
- payment.provider_data['cancellation_info']['cancelled_at'] = timezone.now().isoformat()
533
+ if 'cancellation_info' not in provider_data:
534
+ provider_data['cancellation_info'] = {}
535
+ provider_data['cancellation_info']['reason'] = reason
536
+ provider_data['cancellation_info']['cancelled_at'] = timezone.now().isoformat()
501
537
 
502
- payment.save(update_fields=['status', 'provider_data', 'updated_at'])
538
+ # Update payment using centralized status update method
539
+ extra_fields = PaymentStatusUpdateFields(
540
+ provider_data=provider_data if provider_data != payment.provider_data else None
541
+ )
542
+
543
+ success = self.update_payment_status(payment, 'cancelled', extra_fields)
544
+ if not success:
545
+ return False
503
546
 
504
547
  logger.info(f"Payment cancelled", extra={
505
548
  'payment_id': str(payment.id),
@@ -514,3 +557,77 @@ class PaymentManager(models.Manager):
514
557
  'payment_id': str(payment_id) if hasattr(payment_id, 'id') else payment_id
515
558
  })
516
559
  return False
560
+
561
+ def update_payment_status(
562
+ self,
563
+ payment,
564
+ new_status: str,
565
+ extra_fields: Optional[PaymentStatusUpdateFields] = None
566
+ ) -> bool:
567
+ """
568
+ Update payment status with automatic status_changed_at tracking.
569
+
570
+ Args:
571
+ payment: Payment instance
572
+ new_status: New status value
573
+ extra_fields: Typed extra fields to update
574
+
575
+ Returns:
576
+ bool: True if status was updated
577
+ """
578
+ try:
579
+ old_status = payment.status
580
+
581
+ # Only update if status actually changed
582
+ if old_status != new_status:
583
+ payment.status = new_status
584
+ payment.status_changed_at = timezone.now()
585
+
586
+ # Set completed_at if status changed to completed
587
+ if new_status == 'completed' and not payment.completed_at:
588
+ payment.completed_at = timezone.now()
589
+
590
+ # Update fields list for save()
591
+ update_fields = ['status', 'status_changed_at', 'updated_at']
592
+
593
+ # Apply extra fields if provided
594
+ if extra_fields:
595
+ # Validate extra fields
596
+ if isinstance(extra_fields, dict):
597
+ extra_fields = PaymentStatusUpdateFields(**extra_fields)
598
+
599
+ # Apply non-None fields
600
+ for field_name, field_value in extra_fields.model_dump(exclude_none=True).items():
601
+ if hasattr(payment, field_name):
602
+ setattr(payment, field_name, field_value)
603
+ update_fields.append(field_name)
604
+ else:
605
+ logger.warning(f"Unknown field {field_name} ignored", extra={
606
+ 'payment_id': str(payment.id),
607
+ 'field_name': field_name
608
+ })
609
+
610
+ payment.save(update_fields=update_fields)
611
+
612
+ logger.info(f"Payment status updated", extra={
613
+ 'payment_id': str(payment.id),
614
+ 'old_status': old_status,
615
+ 'new_status': new_status,
616
+ 'updated_fields': update_fields
617
+ })
618
+
619
+ return True
620
+ else:
621
+ logger.debug(f"Payment status unchanged", extra={
622
+ 'payment_id': str(payment.id),
623
+ 'status': new_status
624
+ })
625
+ return False
626
+
627
+ except Exception as e:
628
+ logger.error(f"Failed to update payment status: {e}", extra={
629
+ 'payment_id': str(payment.id),
630
+ 'old_status': old_status if 'old_status' in locals() else 'unknown',
631
+ 'new_status': new_status
632
+ })
633
+ return False
@@ -181,6 +181,13 @@ class UniversalPayment(UUIDTimestampedModel):
181
181
  help_text="When this payment was completed"
182
182
  )
183
183
 
184
+ status_changed_at = models.DateTimeField(
185
+ null=True,
186
+ blank=True,
187
+ db_index=True,
188
+ help_text="When the payment status was last changed"
189
+ )
190
+
184
191
  # Metadata and description
185
192
  description = models.TextField(
186
193
  blank=True,
@@ -243,6 +250,91 @@ class UniversalPayment(UUIDTimestampedModel):
243
250
  def __str__(self):
244
251
  return f"Payment {self.internal_payment_id} - ${self.amount_usd:.2f} {self.currency.code}"
245
252
 
253
+ @property
254
+ def qr_data(self) -> str:
255
+ """Generate QR code data for payment."""
256
+ if not self.pay_address:
257
+ return None
258
+
259
+ # For crypto payments, use proper URI format
260
+ if self.currency and self.currency.currency_type == 'crypto':
261
+ currency_code = self.currency.code.lower()
262
+
263
+ if currency_code == 'btc' and self.pay_amount:
264
+ return f"bitcoin:{self.pay_address}?amount={self.pay_amount}"
265
+ elif currency_code == 'eth' and self.pay_amount:
266
+ return f"ethereum:{self.pay_address}?value={self.pay_amount}"
267
+ elif currency_code == 'ltc' and self.pay_amount:
268
+ return f"litecoin:{self.pay_address}?amount={self.pay_amount}"
269
+
270
+ # Default: just return the address
271
+ return self.pay_address
272
+
273
+ @property
274
+ def formatted_pay_amount(self) -> str:
275
+ """Format cryptocurrency amount with proper precision."""
276
+ if not self.pay_amount or not self.currency:
277
+ return "0"
278
+
279
+ try:
280
+ amount = float(self.pay_amount)
281
+ currency_code = self.currency.code.upper()
282
+
283
+ # Different precision for different currencies
284
+ if currency_code in ['BTC', 'LTC', 'DOGE']:
285
+ # Bitcoin-like currencies - 8 decimal places
286
+ formatted = f"{amount:.8f}".rstrip('0').rstrip('.')
287
+ elif currency_code in ['ETH', 'BNB', 'MATIC']:
288
+ # Ethereum-like currencies - 6 decimal places typically
289
+ formatted = f"{amount:.6f}".rstrip('0').rstrip('.')
290
+ else:
291
+ # Other currencies - 4 decimal places
292
+ formatted = f"{amount:.4f}".rstrip('0').rstrip('.')
293
+
294
+ return formatted if formatted else "0"
295
+
296
+ except (ValueError, TypeError):
297
+ return "0"
298
+
299
+ def get_qr_code_url(self, size=200) -> str:
300
+ """Generate QR code URL using external service."""
301
+ if not self.qr_data:
302
+ return None
303
+
304
+ from urllib.parse import quote
305
+
306
+ # Using QR Server API (free service)
307
+ qr_data_encoded = quote(self.qr_data)
308
+ return f"https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&data={qr_data_encoded}"
309
+
310
+ def get_payment_explorer_link(self) -> str:
311
+ """Generate blockchain explorer link for transaction."""
312
+ if not self.transaction_hash or not self.network:
313
+ return ""
314
+
315
+ # Explorer URL templates for different networks
316
+ explorer_templates = {
317
+ 'bitcoin': 'https://blockstream.info/tx/{txid}',
318
+ 'ethereum': 'https://etherscan.io/tx/{txid}',
319
+ 'tron': 'https://tronscan.org/#/transaction/{txid}',
320
+ 'polygon': 'https://polygonscan.com/tx/{txid}',
321
+ 'bsc': 'https://bscscan.com/tx/{txid}',
322
+ 'binance smart chain': 'https://bscscan.com/tx/{txid}',
323
+ 'litecoin': 'https://blockchair.com/litecoin/transaction/{txid}',
324
+ 'dogecoin': 'https://blockchair.com/dogecoin/transaction/{txid}',
325
+ 'arbitrum': 'https://arbiscan.io/tx/{txid}',
326
+ 'optimism': 'https://optimistic.etherscan.io/tx/{txid}',
327
+ 'avalanche c-chain': 'https://snowtrace.io/tx/{txid}',
328
+ 'base': 'https://basescan.org/tx/{txid}',
329
+ }
330
+
331
+ network_name = self.network.name.lower() if hasattr(self.network, 'name') else str(self.network).lower()
332
+ template = explorer_templates.get(network_name)
333
+ if template:
334
+ return template.format(txid=self.transaction_hash)
335
+
336
+ return ""
337
+
246
338
  def save(self, *args, **kwargs):
247
339
  """Override save to generate internal payment ID."""
248
340
  if not self.internal_payment_id:
@@ -360,3 +452,5 @@ class UniversalPayment(UUIDTimestampedModel):
360
452
  def cancel(self, reason: str = None):
361
453
  """Cancel payment (delegates to manager)."""
362
454
  return self.__class__.objects.cancel_payment(self, reason)
455
+
456
+
@@ -8,8 +8,9 @@ from abc import ABC
8
8
  from typing import Optional, Dict, Any, Type
9
9
  from django.db import transaction
10
10
  from django_cfg.modules.django_logger import get_logger
11
- from ..types import ServiceOperationResult
12
11
 
12
+ from ..types import ServiceOperationResult
13
+ from ...config.django_cfg_integration import PaymentsConfigManager
13
14
 
14
15
  class BaseService(ABC):
15
16
  """
@@ -23,9 +24,8 @@ class BaseService(ABC):
23
24
  self.logger = get_logger(f"services.{self.__class__.__name__.lower()}")
24
25
  self._cache = {}
25
26
 
26
- # Initialize config service
27
- from ...config.constance import get_payment_config_service
28
- self.config_service = get_payment_config_service()
27
+ # Initialize config manager
28
+ self.config_manager = PaymentsConfigManager
29
29
 
30
30
  def _create_success_result(
31
31
  self,
@@ -4,10 +4,9 @@ Currency service for the Universal Payment System v2.0.
4
4
  Handles currency conversion and rate management using django_currency module.
5
5
  """
6
6
 
7
- from typing import Optional, Dict, Any, List
8
- from decimal import Decimal
7
+ from typing import Optional, List
9
8
  from django.utils import timezone
10
- from datetime import timedelta
9
+ from django.db import models
11
10
 
12
11
  from .base import BaseService
13
12
  from ..types import (
@@ -161,29 +160,35 @@ class CurrencyService(BaseService):
161
160
  quote_currency=quote_currency
162
161
  )
163
162
 
164
- def get_supported_currencies(self, provider: Optional[str] = None) -> ServiceOperationResult:
163
+ def get_supported_currencies(self, provider: Optional[str] = None, currency_type: str = 'all') -> ServiceOperationResult:
165
164
  """
166
165
  Get list of supported currencies.
167
166
 
168
167
  Args:
169
168
  provider: Filter by provider (optional)
169
+ currency_type: Filter by currency type ('all', 'crypto', 'fiat')
170
170
 
171
171
  Returns:
172
172
  ServiceOperationResult: List of supported currencies
173
173
  """
174
174
  try:
175
175
  self.logger.debug("Getting supported currencies", extra={
176
- 'provider': provider
176
+ 'provider': provider,
177
+ 'currency_type': currency_type
177
178
  })
178
179
 
179
180
  # Get currencies from database
180
- queryset = Currency.objects.filter(is_enabled=True)
181
+ queryset = Currency.objects.filter(is_active=True)
182
+
183
+ # Filter by currency type
184
+ if currency_type and currency_type != 'all':
185
+ queryset = queryset.filter(currency_type=currency_type)
181
186
 
182
187
  if provider:
183
188
  # Filter by provider support
184
189
  queryset = queryset.filter(
185
- providercurrency__provider=provider,
186
- providercurrency__is_enabled=True
190
+ provider_configs__provider=provider,
191
+ provider_configs__is_enabled=True
187
192
  ).distinct()
188
193
 
189
194
  currencies = queryset.order_by('code')
@@ -197,18 +202,20 @@ class CurrencyService(BaseService):
197
202
  # Add provider-specific info if requested
198
203
  if provider:
199
204
  try:
200
- provider_currency = ProviderCurrency.objects.get(
205
+ provider_currency = ProviderCurrency.objects.filter(
201
206
  currency=currency,
202
207
  provider=provider,
203
208
  is_enabled=True
204
- )
205
- currency_info['provider_info'] = {
206
- 'min_amount': float(provider_currency.min_amount) if provider_currency.min_amount else None,
207
- 'max_amount': float(provider_currency.max_amount) if provider_currency.max_amount else None,
208
- 'network_fee': float(provider_currency.network_fee) if provider_currency.network_fee else None,
209
- 'confirmation_blocks': provider_currency.confirmation_blocks
210
- }
211
- except ProviderCurrency.DoesNotExist:
209
+ ).first()
210
+ if provider_currency:
211
+ currency_info['provider_info'] = {
212
+ 'min_amount': provider_currency.provider_min_amount_usd,
213
+ 'max_amount': provider_currency.provider_max_amount_usd,
214
+ 'fee_percentage': provider_currency.provider_fee_percentage,
215
+ 'fixed_fee_usd': provider_currency.provider_fixed_fee_usd,
216
+ 'confirmation_blocks': provider_currency.provider_confirmation_blocks
217
+ }
218
+ except Exception:
212
219
  pass
213
220
 
214
221
  currency_data.append(currency_info)
@@ -245,7 +252,7 @@ class CurrencyService(BaseService):
245
252
 
246
253
  # Get currency
247
254
  try:
248
- currency = Currency.objects.get(code=currency_code, is_enabled=True)
255
+ currency = Currency.objects.get(code=currency_code, is_active=True)
249
256
  except Currency.DoesNotExist:
250
257
  return self._create_error_result(
251
258
  f"Currency {currency_code} not found or disabled",
@@ -255,7 +262,7 @@ class CurrencyService(BaseService):
255
262
  # Get networks
256
263
  networks = Network.objects.filter(
257
264
  currency_code=currency_code,
258
- is_enabled=True
265
+ is_active=True
259
266
  ).order_by('name')
260
267
 
261
268
  network_data = []
@@ -305,10 +312,10 @@ class CurrencyService(BaseService):
305
312
  if currency_codes:
306
313
  currencies = Currency.objects.filter(
307
314
  code__in=currency_codes,
308
- is_enabled=True
315
+ is_active=True
309
316
  )
310
317
  else:
311
- currencies = Currency.objects.filter(is_enabled=True)
318
+ currencies = Currency.objects.filter(is_active=True)
312
319
 
313
320
  updated_count = 0
314
321
  failed_count = 0
@@ -369,14 +376,14 @@ class CurrencyService(BaseService):
369
376
  try:
370
377
  # Currency counts
371
378
  total_currencies = Currency.objects.count()
372
- enabled_currencies = Currency.objects.filter(is_enabled=True).count()
379
+ enabled_currencies = Currency.objects.filter(is_active=True).count()
373
380
  crypto_currencies = Currency.objects.filter(
374
381
  currency_type=Currency.CurrencyType.CRYPTO,
375
- is_enabled=True
382
+ is_active=True
376
383
  ).count()
377
384
  fiat_currencies = Currency.objects.filter(
378
385
  currency_type=Currency.CurrencyType.FIAT,
379
- is_enabled=True
386
+ is_active=True
380
387
  ).count()
381
388
 
382
389
  # Provider support
@@ -388,7 +395,7 @@ class CurrencyService(BaseService):
388
395
 
389
396
  # Network stats
390
397
  network_stats = Network.objects.filter(
391
- is_enabled=True
398
+ is_active=True
392
399
  ).values('currency_code').annotate(
393
400
  network_count=models.Count('id')
394
401
  ).order_by('-network_count')
@@ -415,14 +422,14 @@ class CurrencyService(BaseService):
415
422
  """Validate that currencies are supported."""
416
423
  try:
417
424
  # Check from_currency
418
- if not Currency.objects.filter(code=from_currency, is_enabled=True).exists():
425
+ if not Currency.objects.filter(code=from_currency, is_active=True).exists():
419
426
  return self._create_error_result(
420
427
  f"Currency {from_currency} not supported",
421
428
  "from_currency_not_supported"
422
429
  )
423
430
 
424
431
  # Check to_currency
425
- if not Currency.objects.filter(code=to_currency, is_enabled=True).exists():
432
+ if not Currency.objects.filter(code=to_currency, is_active=True).exists():
426
433
  return self._create_error_result(
427
434
  f"Currency {to_currency} not supported",
428
435
  "to_currency_not_supported"
@@ -440,7 +447,7 @@ class CurrencyService(BaseService):
440
447
  """Perform currency service health check."""
441
448
  try:
442
449
  # Check database connectivity
443
- currency_count = Currency.objects.filter(is_enabled=True).count()
450
+ currency_count = Currency.objects.filter(is_active=True).count()
444
451
 
445
452
  # Test currency conversion
446
453
  try: