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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- 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 +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- 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/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- 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/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -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/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- 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 +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- 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/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- 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/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- 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/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
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,
|
@@ -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,
|
8
|
-
from decimal import Decimal
|
7
|
+
from typing import Optional, List
|
9
8
|
from django.utils import timezone
|
10
|
-
from
|
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(
|
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
|
-
|
186
|
-
|
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.
|
205
|
+
provider_currency = ProviderCurrency.objects.filter(
|
201
206
|
currency=currency,
|
202
207
|
provider=provider,
|
203
208
|
is_enabled=True
|
204
|
-
)
|
205
|
-
|
206
|
-
'
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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,
|
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
|
-
|
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
|
-
|
315
|
+
is_active=True
|
309
316
|
)
|
310
317
|
else:
|
311
|
-
currencies = Currency.objects.filter(
|
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(
|
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
|
-
|
382
|
+
is_active=True
|
376
383
|
).count()
|
377
384
|
fiat_currencies = Currency.objects.filter(
|
378
385
|
currency_type=Currency.CurrencyType.FIAT,
|
379
|
-
|
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
|
-
|
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,
|
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,
|
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(
|
450
|
+
currency_count = Currency.objects.filter(is_active=True).count()
|
444
451
|
|
445
452
|
# Test currency conversion
|
446
453
|
try:
|