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