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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/payments/admin/networks_admin.py +12 -1
- django_cfg/apps/payments/admin/payments_admin.py +13 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +62 -14
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +33 -3
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +96 -45
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/payment_service.py +265 -38
- django_cfg/apps/payments/services/providers/base.py +209 -3
- django_cfg/apps/payments/services/providers/models/__init__.py +2 -0
- django_cfg/apps/payments/services/providers/models/base.py +25 -2
- django_cfg/apps/payments/services/providers/nowpayments/models.py +2 -2
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +57 -9
- django_cfg/apps/payments/services/providers/registry.py +5 -5
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +6 -1
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +35 -26
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/views/api/currencies.py +3 -0
- django_cfg/apps/payments/views/serializers/currencies.py +18 -5
- django_cfg/apps/tasks/admin/tasks_admin.py +2 -2
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/registry/core.py +4 -9
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -2
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/RECORD +84 -152
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +0 -26
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +0 -28
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +0 -30
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/app_agent_diagnose.py +0 -470
- django_cfg/management/commands/app_agent_generate.py +0 -342
- django_cfg/management/commands/app_agent_info.py +0 -308
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/modules/django_app_agent/__init__.py +0 -87
- django_cfg/modules/django_app_agent/agents/__init__.py +0 -40
- django_cfg/modules/django_app_agent/agents/base/__init__.py +0 -24
- django_cfg/modules/django_app_agent/agents/base/agent.py +0 -354
- django_cfg/modules/django_app_agent/agents/base/context.py +0 -236
- django_cfg/modules/django_app_agent/agents/base/executor.py +0 -430
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +0 -12
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +0 -15
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +0 -147
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +0 -99
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +0 -32
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +0 -290
- django_cfg/modules/django_app_agent/agents/interfaces.py +0 -376
- django_cfg/modules/django_app_agent/core/__init__.py +0 -33
- django_cfg/modules/django_app_agent/core/config.py +0 -300
- django_cfg/modules/django_app_agent/core/exceptions.py +0 -359
- django_cfg/modules/django_app_agent/models/__init__.py +0 -71
- django_cfg/modules/django_app_agent/models/base.py +0 -283
- django_cfg/modules/django_app_agent/models/context.py +0 -496
- django_cfg/modules/django_app_agent/models/enums.py +0 -481
- django_cfg/modules/django_app_agent/models/requests.py +0 -500
- django_cfg/modules/django_app_agent/models/responses.py +0 -585
- django_cfg/modules/django_app_agent/pytest.ini +0 -6
- django_cfg/modules/django_app_agent/services/__init__.py +0 -42
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +0 -30
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +0 -133
- django_cfg/modules/django_app_agent/services/app_generator/context.py +0 -40
- django_cfg/modules/django_app_agent/services/app_generator/main.py +0 -202
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +0 -316
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +0 -125
- django_cfg/modules/django_app_agent/services/base.py +0 -437
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +0 -34
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +0 -141
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +0 -276
- django_cfg/modules/django_app_agent/services/context_builder/main.py +0 -272
- django_cfg/modules/django_app_agent/services/context_builder/models.py +0 -40
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +0 -85
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +0 -31
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +0 -311
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +0 -221
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +0 -59
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +0 -94
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +0 -28
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +0 -273
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +0 -111
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +0 -251
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +0 -347
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +0 -356
- django_cfg/modules/django_app_agent/services/report_service.py +0 -332
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +0 -18
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +0 -236
- django_cfg/modules/django_app_agent/services/template_manager/main.py +0 -159
- django_cfg/modules/django_app_agent/services/template_manager/models.py +0 -36
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +0 -100
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +0 -105
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +0 -31
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +0 -44
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +0 -81
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +0 -107
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +0 -139
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +0 -91
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +0 -195
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +0 -35
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +0 -211
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +0 -200
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +0 -25
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +0 -333
- django_cfg/modules/django_app_agent/services/validation_service/main.py +0 -242
- django_cfg/modules/django_app_agent/services/validation_service/models.py +0 -66
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +0 -352
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +0 -272
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +0 -203
- django_cfg/modules/django_app_agent/ui/__init__.py +0 -25
- django_cfg/modules/django_app_agent/ui/cli.py +0 -419
- django_cfg/modules/django_app_agent/ui/rich_components.py +0 -622
- django_cfg/modules/django_app_agent/utils/__init__.py +0 -38
- django_cfg/modules/django_app_agent/utils/logging.py +0 -360
- django_cfg/modules/django_app_agent/utils/validation.py +0 -417
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
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
|
-
|
404
|
-
|
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
|
-
#
|
442
|
-
payment.
|
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
|
445
|
-
|
473
|
+
if 'error_info' not in provider_data:
|
474
|
+
provider_data['error_info'] = {}
|
446
475
|
if reason:
|
447
|
-
|
476
|
+
provider_data['error_info']['reason'] = reason
|
448
477
|
if error_code:
|
449
|
-
|
450
|
-
|
478
|
+
provider_data['error_info']['code'] = error_code
|
479
|
+
provider_data['error_info']['failed_at'] = timezone.now().isoformat()
|
451
480
|
|
452
|
-
payment
|
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
|
-
#
|
495
|
-
payment.
|
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
|
498
|
-
|
499
|
-
|
500
|
-
|
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
|
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
|
27
|
-
|
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,
|