django-cfg 1.3.9__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 (187) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/networks_admin.py +12 -1
  3. django_cfg/apps/payments/admin/payments_admin.py +13 -0
  4. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +62 -14
  5. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  6. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  7. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  8. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  9. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  10. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  11. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  12. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +33 -3
  13. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  14. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +96 -45
  15. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  16. django_cfg/apps/payments/config/__init__.py +14 -15
  17. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  18. django_cfg/apps/payments/config/helpers.py +8 -13
  19. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  20. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  21. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  22. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  23. django_cfg/apps/payments/models/payments.py +94 -0
  24. django_cfg/apps/payments/services/core/base.py +4 -4
  25. django_cfg/apps/payments/services/core/payment_service.py +265 -38
  26. django_cfg/apps/payments/services/providers/base.py +209 -3
  27. django_cfg/apps/payments/services/providers/models/__init__.py +2 -0
  28. django_cfg/apps/payments/services/providers/models/base.py +25 -2
  29. django_cfg/apps/payments/services/providers/nowpayments/models.py +2 -2
  30. django_cfg/apps/payments/services/providers/nowpayments/provider.py +57 -9
  31. django_cfg/apps/payments/services/providers/registry.py +5 -5
  32. django_cfg/apps/payments/services/types/requests.py +19 -7
  33. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  34. django_cfg/apps/payments/static/payments/js/api-client.js +6 -1
  35. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  36. django_cfg/apps/payments/static/payments/js/payment-form.js +35 -26
  37. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  38. django_cfg/apps/payments/urls.py +3 -2
  39. django_cfg/apps/payments/views/api/currencies.py +3 -0
  40. django_cfg/apps/payments/views/serializers/currencies.py +18 -5
  41. django_cfg/apps/tasks/admin/tasks_admin.py +2 -2
  42. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  43. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  44. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  45. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  46. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  47. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  48. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  49. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  50. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  51. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  52. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  53. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  54. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  55. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  56. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  57. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  58. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  59. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  60. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  61. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  62. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  63. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  64. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  65. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  66. django_cfg/apps/tasks/urls.py +2 -2
  67. django_cfg/apps/tasks/urls_admin.py +2 -2
  68. django_cfg/apps/tasks/utils/__init__.py +1 -0
  69. django_cfg/apps/tasks/utils/simulator.py +356 -0
  70. django_cfg/apps/tasks/views/__init__.py +16 -0
  71. django_cfg/apps/tasks/views/api.py +569 -0
  72. django_cfg/apps/tasks/views/dashboard.py +58 -0
  73. django_cfg/core/integration/__init__.py +21 -0
  74. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  75. django_cfg/models/constance.py +0 -11
  76. django_cfg/models/payments.py +137 -3
  77. django_cfg/modules/django_tasks.py +54 -21
  78. django_cfg/registry/core.py +4 -9
  79. django_cfg/template_archive/django_sample.zip +0 -0
  80. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -2
  81. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/RECORD +84 -152
  82. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  83. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  84. django_cfg/apps/payments/config/constance/fields.py +0 -69
  85. django_cfg/apps/payments/config/constance/settings.py +0 -160
  86. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +0 -26
  87. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +0 -28
  88. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +0 -30
  89. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  90. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  91. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  92. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  93. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  94. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  95. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  96. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  97. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  98. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  99. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  100. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  101. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  102. django_cfg/apps/tasks/views.py +0 -461
  103. django_cfg/management/commands/app_agent_diagnose.py +0 -470
  104. django_cfg/management/commands/app_agent_generate.py +0 -342
  105. django_cfg/management/commands/app_agent_info.py +0 -308
  106. django_cfg/management/commands/auto_generate.py +0 -486
  107. django_cfg/modules/django_app_agent/__init__.py +0 -87
  108. django_cfg/modules/django_app_agent/agents/__init__.py +0 -40
  109. django_cfg/modules/django_app_agent/agents/base/__init__.py +0 -24
  110. django_cfg/modules/django_app_agent/agents/base/agent.py +0 -354
  111. django_cfg/modules/django_app_agent/agents/base/context.py +0 -236
  112. django_cfg/modules/django_app_agent/agents/base/executor.py +0 -430
  113. django_cfg/modules/django_app_agent/agents/generation/__init__.py +0 -12
  114. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +0 -15
  115. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +0 -147
  116. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +0 -99
  117. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +0 -32
  118. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +0 -290
  119. django_cfg/modules/django_app_agent/agents/interfaces.py +0 -376
  120. django_cfg/modules/django_app_agent/core/__init__.py +0 -33
  121. django_cfg/modules/django_app_agent/core/config.py +0 -300
  122. django_cfg/modules/django_app_agent/core/exceptions.py +0 -359
  123. django_cfg/modules/django_app_agent/models/__init__.py +0 -71
  124. django_cfg/modules/django_app_agent/models/base.py +0 -283
  125. django_cfg/modules/django_app_agent/models/context.py +0 -496
  126. django_cfg/modules/django_app_agent/models/enums.py +0 -481
  127. django_cfg/modules/django_app_agent/models/requests.py +0 -500
  128. django_cfg/modules/django_app_agent/models/responses.py +0 -585
  129. django_cfg/modules/django_app_agent/pytest.ini +0 -6
  130. django_cfg/modules/django_app_agent/services/__init__.py +0 -42
  131. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +0 -30
  132. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +0 -133
  133. django_cfg/modules/django_app_agent/services/app_generator/context.py +0 -40
  134. django_cfg/modules/django_app_agent/services/app_generator/main.py +0 -202
  135. django_cfg/modules/django_app_agent/services/app_generator/structure.py +0 -316
  136. django_cfg/modules/django_app_agent/services/app_generator/validation.py +0 -125
  137. django_cfg/modules/django_app_agent/services/base.py +0 -437
  138. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +0 -34
  139. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +0 -141
  140. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +0 -276
  141. django_cfg/modules/django_app_agent/services/context_builder/main.py +0 -272
  142. django_cfg/modules/django_app_agent/services/context_builder/models.py +0 -40
  143. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +0 -85
  144. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +0 -31
  145. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +0 -311
  146. django_cfg/modules/django_app_agent/services/project_scanner/main.py +0 -221
  147. django_cfg/modules/django_app_agent/services/project_scanner/models.py +0 -59
  148. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +0 -94
  149. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +0 -28
  150. django_cfg/modules/django_app_agent/services/questioning_service/main.py +0 -273
  151. django_cfg/modules/django_app_agent/services/questioning_service/models.py +0 -111
  152. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +0 -251
  153. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +0 -347
  154. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +0 -356
  155. django_cfg/modules/django_app_agent/services/report_service.py +0 -332
  156. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +0 -18
  157. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +0 -236
  158. django_cfg/modules/django_app_agent/services/template_manager/main.py +0 -159
  159. django_cfg/modules/django_app_agent/services/template_manager/models.py +0 -36
  160. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +0 -100
  161. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +0 -105
  162. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +0 -31
  163. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +0 -44
  164. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +0 -81
  165. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +0 -107
  166. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +0 -139
  167. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +0 -91
  168. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +0 -195
  169. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +0 -35
  170. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +0 -211
  171. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +0 -200
  172. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +0 -25
  173. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +0 -333
  174. django_cfg/modules/django_app_agent/services/validation_service/main.py +0 -242
  175. django_cfg/modules/django_app_agent/services/validation_service/models.py +0 -66
  176. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +0 -352
  177. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +0 -272
  178. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +0 -203
  179. django_cfg/modules/django_app_agent/ui/__init__.py +0 -25
  180. django_cfg/modules/django_app_agent/ui/cli.py +0 -419
  181. django_cfg/modules/django_app_agent/ui/rich_components.py +0 -622
  182. django_cfg/modules/django_app_agent/utils/__init__.py +0 -38
  183. django_cfg/modules/django_app_agent/utils/logging.py +0 -360
  184. django_cfg/modules/django_app_agent/utils/validation.py +0 -417
  185. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  186. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  187. {django_cfg-1.3.9.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,