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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/inlines.py +11 -5
- 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.13.dist-info}/METADATA +2 -2
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/RECORD +85 -153
- 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.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
@@ -48,6 +48,7 @@ class UserActivityInline(TabularInline):
|
|
48
48
|
"""Enhanced inline for user activities."""
|
49
49
|
model = UserActivity
|
50
50
|
extra = 0
|
51
|
+
max_num = 10 # Limit to 10 most recent activities
|
51
52
|
readonly_fields = ["created_at", "activity_type", "description"]
|
52
53
|
fields = ["activity_type", "description", "ip_address", "created_at"]
|
53
54
|
ordering = ["-created_at"]
|
@@ -57,7 +58,8 @@ class UserActivityInline(TabularInline):
|
|
57
58
|
# Show only recent activities to avoid performance issues
|
58
59
|
def get_queryset(self, request):
|
59
60
|
qs = super().get_queryset(request)
|
60
|
-
|
61
|
+
# Don't slice here - let Django handle formset filtering first
|
62
|
+
return qs.order_by('-created_at')
|
61
63
|
|
62
64
|
def has_add_permission(self, request, obj=None):
|
63
65
|
return False
|
@@ -88,6 +90,7 @@ class UserEmailLogInline(TabularInline):
|
|
88
90
|
super().__init__(*args, **kwargs)
|
89
91
|
|
90
92
|
extra = 0
|
93
|
+
max_num = 15 # Limit to 15 most recent emails
|
91
94
|
readonly_fields = ["newsletter", "campaign", "recipient", "subject", "status", "created_at", "sent_at"]
|
92
95
|
fields = ["newsletter", "campaign", "subject", "status", "created_at", "sent_at"]
|
93
96
|
ordering = ["-created_at"]
|
@@ -99,7 +102,8 @@ class UserEmailLogInline(TabularInline):
|
|
99
102
|
if not self.model:
|
100
103
|
return self.model.objects.none()
|
101
104
|
qs = super().get_queryset(request)
|
102
|
-
|
105
|
+
# Don't slice here - let Django handle formset filtering first
|
106
|
+
return qs.order_by('-created_at')
|
103
107
|
|
104
108
|
def has_add_permission(self, request, obj=None):
|
105
109
|
return False
|
@@ -139,8 +143,9 @@ class UserSupportTicketsInline(TabularInline):
|
|
139
143
|
super().__init__(*args, **kwargs)
|
140
144
|
|
141
145
|
extra = 0
|
142
|
-
|
143
|
-
|
146
|
+
max_num = 10 # Limit to 10 most recent tickets
|
147
|
+
readonly_fields = ["uuid", "subject", "status", "created_at"]
|
148
|
+
fields = ["uuid", "subject", "status", "created_at"]
|
144
149
|
ordering = ["-created_at"]
|
145
150
|
verbose_name = "Support Ticket"
|
146
151
|
verbose_name_plural = "Support Tickets"
|
@@ -150,7 +155,8 @@ class UserSupportTicketsInline(TabularInline):
|
|
150
155
|
if not self.model:
|
151
156
|
return self.model.objects.none()
|
152
157
|
qs = super().get_queryset(request)
|
153
|
-
|
158
|
+
# Don't slice here - let Django handle formset filtering first
|
159
|
+
return qs.order_by('-created_at')
|
154
160
|
|
155
161
|
def has_add_permission(self, request, obj=None):
|
156
162
|
return False
|
@@ -143,6 +143,7 @@ class ProviderCurrencyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
143
143
|
# List configuration
|
144
144
|
list_display = [
|
145
145
|
'currency_display',
|
146
|
+
'provider_currency_code_display',
|
146
147
|
'network_display',
|
147
148
|
'provider_display',
|
148
149
|
'fees_display',
|
@@ -163,7 +164,8 @@ class ProviderCurrencyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
163
164
|
'currency__name',
|
164
165
|
'currency__symbol',
|
165
166
|
'network__name',
|
166
|
-
'provider'
|
167
|
+
'provider',
|
168
|
+
'provider_currency_code'
|
167
169
|
]
|
168
170
|
|
169
171
|
readonly_fields = [
|
@@ -181,6 +183,15 @@ class ProviderCurrencyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
181
183
|
icon=Icons.CURRENCY_BITCOIN
|
182
184
|
)
|
183
185
|
|
186
|
+
@display(description="Provider Code")
|
187
|
+
def provider_currency_code_display(self, obj: ProviderCurrency):
|
188
|
+
"""Provider currency code display."""
|
189
|
+
return StatusBadge.create(
|
190
|
+
text=obj.provider_currency_code,
|
191
|
+
variant="warning",
|
192
|
+
icon=Icons.CODE
|
193
|
+
)
|
194
|
+
|
184
195
|
@display(description="Network")
|
185
196
|
def network_display(self, obj: ProviderCurrency):
|
186
197
|
"""Network display."""
|
@@ -56,6 +56,7 @@ class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
56
56
|
'amount_display',
|
57
57
|
'status_display',
|
58
58
|
'provider_display',
|
59
|
+
'status_changed_display',
|
59
60
|
'created_display'
|
60
61
|
]
|
61
62
|
|
@@ -78,6 +79,7 @@ class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
78
79
|
'internal_payment_id',
|
79
80
|
'created_at',
|
80
81
|
'updated_at',
|
82
|
+
'status_changed_at',
|
81
83
|
'payment_details_display'
|
82
84
|
]
|
83
85
|
|
@@ -168,6 +170,17 @@ class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
168
170
|
DateTimeDisplayConfig(show_relative=True, show_seconds=False)
|
169
171
|
)
|
170
172
|
|
173
|
+
@display(description="Status Changed")
|
174
|
+
def status_changed_display(self, obj):
|
175
|
+
"""Status changed time display."""
|
176
|
+
if not obj.status_changed_at:
|
177
|
+
return "-"
|
178
|
+
return self.display_datetime_relative(
|
179
|
+
obj,
|
180
|
+
'status_changed_at',
|
181
|
+
DateTimeDisplayConfig(show_relative=True, show_seconds=False)
|
182
|
+
)
|
183
|
+
|
171
184
|
# Readonly field displays
|
172
185
|
def payment_details_display(self, obj):
|
173
186
|
"""Detailed payment information for detail view."""
|
@@ -6,6 +6,8 @@ DRF serializers for payment management in admin dashboard.
|
|
6
6
|
|
7
7
|
from rest_framework import serializers
|
8
8
|
from django.contrib.auth import get_user_model
|
9
|
+
from django.contrib.humanize.templatetags.humanize import naturaltime
|
10
|
+
|
9
11
|
from ...models import UniversalPayment
|
10
12
|
|
11
13
|
User = get_user_model()
|
@@ -90,6 +92,7 @@ class AdminPaymentDetailSerializer(serializers.Serializer):
|
|
90
92
|
security_nonce = serializers.CharField(read_only=True)
|
91
93
|
expires_at = serializers.DateTimeField(read_only=True)
|
92
94
|
completed_at = serializers.DateTimeField(read_only=True)
|
95
|
+
status_changed_at = serializers.DateTimeField(read_only=True)
|
93
96
|
description = serializers.CharField(read_only=True)
|
94
97
|
callback_url = serializers.URLField(read_only=True)
|
95
98
|
cancel_url = serializers.URLField(read_only=True)
|
@@ -117,7 +120,6 @@ class AdminPaymentDetailSerializer(serializers.Serializer):
|
|
117
120
|
|
118
121
|
def get_age(self, obj):
|
119
122
|
"""Get human-readable age of payment."""
|
120
|
-
from django.contrib.humanize.templatetags.humanize import naturaltime
|
121
123
|
return naturaltime(obj.created_at)
|
122
124
|
|
123
125
|
|
@@ -128,7 +130,7 @@ class AdminPaymentCreateSerializer(serializers.Serializer):
|
|
128
130
|
"""
|
129
131
|
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
|
130
132
|
amount_usd = serializers.FloatField(min_value=1.0, max_value=100000.0)
|
131
|
-
currency_code = serializers.CharField(max_length=
|
133
|
+
currency_code = serializers.CharField(max_length=20, help_text="Provider currency code (e.g., BTC, ZROERC20)", write_only=True)
|
132
134
|
provider = serializers.CharField(max_length=50)
|
133
135
|
description = serializers.CharField(required=False, allow_blank=True)
|
134
136
|
callback_url = serializers.URLField(required=False, allow_blank=True)
|
@@ -143,22 +145,67 @@ class AdminPaymentCreateSerializer(serializers.Serializer):
|
|
143
145
|
return value
|
144
146
|
|
145
147
|
def create(self, validated_data):
|
146
|
-
"""Create payment
|
147
|
-
from django_cfg.apps.payments.
|
148
|
-
import
|
148
|
+
"""Create payment using PaymentService for proper provider integration."""
|
149
|
+
from django_cfg.apps.payments.services.core.payment_service import PaymentService
|
150
|
+
from django_cfg.apps.payments.services.types.requests import PaymentCreateRequest
|
151
|
+
from django_cfg.apps.payments.models import ProviderCurrency
|
152
|
+
|
153
|
+
# Extract provider_currency_code and find original currency
|
154
|
+
provider_currency_code = validated_data.pop('currency_code')
|
149
155
|
|
150
|
-
#
|
151
|
-
currency_code = validated_data.pop('currency_code')
|
156
|
+
# Find the ProviderCurrency to get original currency code
|
152
157
|
try:
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
provider_currency = ProviderCurrency.objects.select_related('currency').get(
|
159
|
+
provider_currency_code=provider_currency_code,
|
160
|
+
provider=validated_data['provider'],
|
161
|
+
is_enabled=True
|
162
|
+
)
|
163
|
+
original_currency_code = provider_currency.currency.code
|
164
|
+
except ProviderCurrency.DoesNotExist:
|
165
|
+
raise serializers.ValidationError(f"Provider currency {provider_currency_code} not found for {validated_data['provider']}")
|
156
166
|
|
157
|
-
#
|
158
|
-
|
159
|
-
|
167
|
+
# Create PaymentCreateRequest for the service
|
168
|
+
payment_request = PaymentCreateRequest(
|
169
|
+
user_id=validated_data['user'].id,
|
170
|
+
amount_usd=validated_data['amount_usd'],
|
171
|
+
currency_code=original_currency_code, # Use original currency code
|
172
|
+
provider=validated_data['provider'],
|
173
|
+
description=validated_data.get('description', ''),
|
174
|
+
callback_url=validated_data.get('callback_url', ''),
|
175
|
+
cancel_url=validated_data.get('cancel_url', ''),
|
176
|
+
metadata={'provider_currency_code': provider_currency_code} # Store provider code in metadata
|
177
|
+
)
|
160
178
|
|
161
|
-
|
179
|
+
# Use PaymentService to create payment with provider integration
|
180
|
+
payment_service = PaymentService()
|
181
|
+
result = payment_service.create_payment(payment_request)
|
182
|
+
|
183
|
+
if result.success:
|
184
|
+
# Get the created payment object from database using payment_id
|
185
|
+
from django_cfg.apps.payments.models import UniversalPayment
|
186
|
+
payment = UniversalPayment.objects.get(id=result.payment_id)
|
187
|
+
return payment
|
188
|
+
else:
|
189
|
+
raise serializers.ValidationError(f"Payment creation failed: {result.message}")
|
190
|
+
|
191
|
+
def to_representation(self, instance):
|
192
|
+
"""Return created payment with ID and currency info."""
|
193
|
+
if instance:
|
194
|
+
return {
|
195
|
+
'id': str(instance.id),
|
196
|
+
'user': instance.user.id,
|
197
|
+
'amount_usd': instance.amount_usd,
|
198
|
+
'currency_code': instance.currency.code if instance.currency else None,
|
199
|
+
'currency_name': instance.currency.name if instance.currency else None,
|
200
|
+
'provider': instance.provider,
|
201
|
+
'description': instance.description,
|
202
|
+
'callback_url': instance.callback_url,
|
203
|
+
'cancel_url': instance.cancel_url,
|
204
|
+
'internal_payment_id': instance.internal_payment_id,
|
205
|
+
'status': instance.status,
|
206
|
+
'created_at': instance.created_at.isoformat() if instance.created_at else None
|
207
|
+
}
|
208
|
+
return super().to_representation(instance)
|
162
209
|
|
163
210
|
|
164
211
|
class AdminPaymentUpdateSerializer(serializers.ModelSerializer):
|
@@ -178,6 +225,7 @@ class AdminPaymentUpdateSerializer(serializers.ModelSerializer):
|
|
178
225
|
if value != UniversalPayment.PaymentStatus.COMPLETED:
|
179
226
|
raise serializers.ValidationError("Cannot change status of completed payment")
|
180
227
|
return value
|
228
|
+
|
181
229
|
|
182
230
|
|
183
231
|
class AdminPaymentStatsSerializer(serializers.Serializer):
|
@@ -0,0 +1,121 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Payment Card Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/payment_card.html' with payment=payment show_actions=True %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment: UniversalPayment object
|
9
|
+
- show_actions: Boolean to show/hide action buttons (default: False)
|
10
|
+
{% endcomment %}
|
11
|
+
|
12
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6"
|
13
|
+
data-payment-id="{{ payment.id }}">
|
14
|
+
|
15
|
+
<!-- Header -->
|
16
|
+
<div class="flex items-center justify-between mb-4">
|
17
|
+
<div class="payment-id">
|
18
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Payment</span>
|
19
|
+
<p class="text-lg font-semibold text-gray-900 dark:text-white">
|
20
|
+
#{{ payment.internal_payment_id|default:payment.id|truncatechars:8 }}
|
21
|
+
</p>
|
22
|
+
</div>
|
23
|
+
{% include 'payments/components/status_badge.html' with payment=payment %}
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<!-- Amount -->
|
27
|
+
<div class="payment-amount mb-4">
|
28
|
+
<div class="flex items-baseline">
|
29
|
+
<span class="text-3xl font-bold text-gray-900 dark:text-white">
|
30
|
+
${{ payment.amount_usd|floatformat:2 }}
|
31
|
+
</span>
|
32
|
+
<span class="ml-2 text-lg text-gray-500 dark:text-gray-400">USD</span>
|
33
|
+
</div>
|
34
|
+
{% if payment.currency.code != 'USD' and payment.pay_amount %}
|
35
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
36
|
+
{{ payment.pay_amount|floatformat:8 }} {{ payment.currency.code }}
|
37
|
+
</div>
|
38
|
+
{% endif %}
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<!-- Payment Details -->
|
42
|
+
<div class="payment-details space-y-2 mb-4">
|
43
|
+
<div class="flex justify-between">
|
44
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Provider</span>
|
45
|
+
<div class="flex items-center">
|
46
|
+
<div class="w-2 h-2 rounded-full mr-2
|
47
|
+
{% if payment.provider == 'cryptapi' %}bg-orange-500
|
48
|
+
{% elif payment.provider == 'cryptomus' %}bg-blue-500
|
49
|
+
{% elif payment.provider == 'stripe' %}bg-purple-500
|
50
|
+
{% elif payment.provider == 'nowpayments' %}bg-green-500
|
51
|
+
{% else %}bg-gray-500{% endif %}">
|
52
|
+
</div>
|
53
|
+
<span class="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
54
|
+
{{ payment.provider }}
|
55
|
+
</span>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
<div class="flex justify-between">
|
59
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Created</span>
|
60
|
+
<span class="text-sm text-gray-900 dark:text-white">
|
61
|
+
{{ payment.created_at|date:"M d, H:i" }}
|
62
|
+
</span>
|
63
|
+
</div>
|
64
|
+
{% if payment.status_changed_at %}
|
65
|
+
<div class="flex justify-between">
|
66
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Status Changed</span>
|
67
|
+
<span class="text-sm text-gray-900 dark:text-white">
|
68
|
+
{{ payment.status_changed_at|date:"M d, H:i" }}
|
69
|
+
</span>
|
70
|
+
</div>
|
71
|
+
{% endif %}
|
72
|
+
{% if payment.processed_at %}
|
73
|
+
<div class="flex justify-between">
|
74
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Processed</span>
|
75
|
+
<span class="text-sm text-gray-900 dark:text-white">
|
76
|
+
{{ payment.processed_at|date:"M d, H:i" }}
|
77
|
+
</span>
|
78
|
+
</div>
|
79
|
+
{% endif %}
|
80
|
+
{% if payment.pay_address %}
|
81
|
+
<div class="flex justify-between">
|
82
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Address</span>
|
83
|
+
<span class="text-sm font-mono text-gray-900 dark:text-white">
|
84
|
+
{{ payment.pay_address|truncatechars:16 }}
|
85
|
+
<button onclick="navigator.clipboard.writeText('{{ payment.pay_address }}')"
|
86
|
+
class="ml-1 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
87
|
+
<span class="material-icons-outlined text-xs">content_copy</span>
|
88
|
+
</button>
|
89
|
+
</span>
|
90
|
+
</div>
|
91
|
+
{% endif %}
|
92
|
+
</div>
|
93
|
+
|
94
|
+
<!-- Progress Bar -->
|
95
|
+
{% include 'payments/components/progress_bar.html' with payment=payment %}
|
96
|
+
|
97
|
+
<!-- Actions -->
|
98
|
+
{% if show_actions %}
|
99
|
+
<div class="payment-actions mt-4 flex space-x-2">
|
100
|
+
<a href="{% url 'cfg_payments_admin:payment-detail' payment.id %}"
|
101
|
+
class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200 text-center">
|
102
|
+
<span class="material-icons-outlined text-sm mr-1">visibility</span>
|
103
|
+
View Details
|
104
|
+
</a>
|
105
|
+
{% if payment.status == 'pending' or payment.status == 'confirming' %}
|
106
|
+
<button class="px-3 py-1.5 text-xs bg-red-600 text-white hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 rounded-md transition-colors duration-200"
|
107
|
+
onclick="cancelPayment('{{ payment.id }}')">
|
108
|
+
<span class="material-icons-outlined text-sm mr-1">cancel</span>
|
109
|
+
Cancel
|
110
|
+
</button>
|
111
|
+
{% endif %}
|
112
|
+
{% if payment.pay_address %}
|
113
|
+
<button class="px-3 py-1.5 text-xs bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 rounded-md transition-colors duration-200"
|
114
|
+
onclick="showQRCode('{{ payment.id }}')">
|
115
|
+
<span class="material-icons-outlined text-sm mr-1">qr_code</span>
|
116
|
+
QR Code
|
117
|
+
</button>
|
118
|
+
{% endif %}
|
119
|
+
</div>
|
120
|
+
{% endif %}
|
121
|
+
</div>
|
@@ -0,0 +1,95 @@
|
|
1
|
+
{% load payment_tags %}
|
2
|
+
|
3
|
+
<!-- Payment QR Code Component -->
|
4
|
+
<div class="flex flex-col items-center space-y-4 p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
5
|
+
{% if payment.pay_address %}
|
6
|
+
<!-- QR Code Image -->
|
7
|
+
<div class="w-48 h-48 bg-gray-100 dark:bg-gray-700 rounded flex items-center justify-center">
|
8
|
+
{% if payment.get_qr_code_url %}
|
9
|
+
<img src="{{ payment.get_qr_code_url }}"
|
10
|
+
alt="QR Code for {{ payment.pay_address }}"
|
11
|
+
class="w-48 h-48 rounded"
|
12
|
+
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
13
|
+
<div class="hidden w-48 h-48 items-center justify-center">
|
14
|
+
<span class="material-icons text-gray-400 text-4xl">qr_code_off</span>
|
15
|
+
</div>
|
16
|
+
{% else %}
|
17
|
+
<span class="material-icons text-gray-400 text-4xl">qr_code_off</span>
|
18
|
+
{% endif %}
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<!-- Payment Details -->
|
22
|
+
<div class="text-center space-y-2">
|
23
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
24
|
+
Payment Address
|
25
|
+
</p>
|
26
|
+
<p class="text-xs font-mono text-gray-600 dark:text-gray-400 break-all max-w-xs">
|
27
|
+
{{ payment.pay_address }}
|
28
|
+
</p>
|
29
|
+
{% if payment.pay_amount %}
|
30
|
+
<p class="text-xs text-gray-500 dark:text-gray-500">
|
31
|
+
Amount: {{ payment.formatted_pay_amount }} {{ payment.currency.code }}
|
32
|
+
</p>
|
33
|
+
{% endif %}
|
34
|
+
|
35
|
+
<!-- QR Data Preview -->
|
36
|
+
{% if payment.qr_data != payment.pay_address %}
|
37
|
+
<p class="text-xs text-blue-600 dark:text-blue-400">
|
38
|
+
QR contains: {{ payment.qr_data|truncatechars:50 }}
|
39
|
+
</p>
|
40
|
+
{% endif %}
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<!-- Action Buttons -->
|
44
|
+
<div class="flex space-x-2 pt-2">
|
45
|
+
<button class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200"
|
46
|
+
onclick="copyToClipboard('{{ payment.pay_address }}', 'Address copied!')">
|
47
|
+
<span class="material-icons text-sm mr-1">content_copy</span>
|
48
|
+
Copy Address
|
49
|
+
</button>
|
50
|
+
{% if payment.pay_amount %}
|
51
|
+
<button class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200"
|
52
|
+
onclick="copyToClipboard('{{ payment.pay_amount }}', 'Amount copied!')">
|
53
|
+
<span class="material-icons text-sm mr-1">content_copy</span>
|
54
|
+
Copy Amount
|
55
|
+
</button>
|
56
|
+
{% endif %}
|
57
|
+
{% if payment.qr_data %}
|
58
|
+
<button class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200"
|
59
|
+
onclick="copyToClipboard('{{ payment.qr_data }}', 'QR data copied!')">
|
60
|
+
<span class="material-icons text-sm mr-1">qr_code</span>
|
61
|
+
Copy QR Data
|
62
|
+
</button>
|
63
|
+
{% endif %}
|
64
|
+
</div>
|
65
|
+
|
66
|
+
|
67
|
+
{% else %}
|
68
|
+
<div class="text-center py-8">
|
69
|
+
<span class="material-icons text-gray-400 text-4xl mb-2">qr_code_off</span>
|
70
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
71
|
+
QR code not available for this payment
|
72
|
+
</p>
|
73
|
+
</div>
|
74
|
+
{% endif %}
|
75
|
+
</div>
|
76
|
+
|
77
|
+
<!-- Copy to clipboard utility -->
|
78
|
+
<script>
|
79
|
+
function copyToClipboard(text, message) {
|
80
|
+
navigator.clipboard.writeText(text).then(function() {
|
81
|
+
// Show success message (you can customize this)
|
82
|
+
const toast = document.createElement('div');
|
83
|
+
toast.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
84
|
+
toast.textContent = message || 'Copied to clipboard!';
|
85
|
+
document.body.appendChild(toast);
|
86
|
+
|
87
|
+
setTimeout(() => {
|
88
|
+
document.body.removeChild(toast);
|
89
|
+
}, 2000);
|
90
|
+
}).catch(function(err) {
|
91
|
+
console.error('Failed to copy: ', err);
|
92
|
+
alert('Failed to copy to clipboard');
|
93
|
+
});
|
94
|
+
}
|
95
|
+
</script>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Payment Progress Bar Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/progress_bar.html' with payment=payment %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment: UniversalPayment object
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<div class="payment-progress">
|
12
|
+
<!-- Progress Bar -->
|
13
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
14
|
+
<div class="h-2 rounded-full transition-all duration-500 ease-out
|
15
|
+
{% if payment.status == 'completed' %}bg-green-600
|
16
|
+
{% elif payment.status == 'failed' %}bg-red-600
|
17
|
+
{% elif payment.status == 'pending' %}bg-yellow-600
|
18
|
+
{% elif payment.status == 'confirming' %}bg-blue-600
|
19
|
+
{% elif payment.status == 'cancelled' %}bg-gray-600
|
20
|
+
{% else %}bg-blue-600{% endif %}"
|
21
|
+
style="width: {% if payment.status == 'completed' %}100{% elif payment.status == 'failed' or payment.status == 'cancelled' %}100{% elif payment.status == 'confirming' %}75{% elif payment.status == 'pending' %}25{% else %}0{% endif %}%">
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<!-- Progress Text -->
|
26
|
+
<div class="flex justify-between items-center mt-2 text-xs text-gray-500 dark:text-gray-400">
|
27
|
+
<span>
|
28
|
+
{% if payment.status == 'completed' %}100% Complete
|
29
|
+
{% elif payment.status == 'failed' %}Failed
|
30
|
+
{% elif payment.status == 'cancelled' %}Cancelled
|
31
|
+
{% elif payment.status == 'confirming' %}75% Confirming
|
32
|
+
{% elif payment.status == 'pending' %}25% Pending
|
33
|
+
{% else %}0% Initiated{% endif %}
|
34
|
+
</span>
|
35
|
+
<span>{{ payment.get_status_display }}</span>
|
36
|
+
</div>
|
37
|
+
</div>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Provider Statistics Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/provider_stats.html' with provider_stats=provider_stats %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- provider_stats: List of provider statistics objects
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<div class="space-y-4">
|
12
|
+
{% for stat in provider_stats %}
|
13
|
+
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
14
|
+
<div class="flex items-center space-x-3">
|
15
|
+
<!-- Provider Icon -->
|
16
|
+
<div class="w-8 h-8 rounded-full flex items-center justify-center
|
17
|
+
{% if stat.provider == 'cryptapi' %}bg-orange-500
|
18
|
+
{% elif stat.provider == 'cryptomus' %}bg-blue-500
|
19
|
+
{% elif stat.provider == 'stripe' %}bg-purple-500
|
20
|
+
{% elif stat.provider == 'nowpayments' %}bg-green-500
|
21
|
+
{% else %}bg-gray-500{% endif %}">
|
22
|
+
<span class="material-icons-outlined text-white text-sm">payment</span>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<!-- Provider Info -->
|
26
|
+
<div>
|
27
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
28
|
+
{{ stat.provider }}
|
29
|
+
</p>
|
30
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
31
|
+
{{ stat.count }} payment{{ stat.count|pluralize }}
|
32
|
+
</p>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<!-- Statistics -->
|
37
|
+
<div class="text-right">
|
38
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
39
|
+
${{ stat.volume|floatformat:2|default:"0.00" }}
|
40
|
+
</p>
|
41
|
+
<div class="flex items-center space-x-2 mt-1">
|
42
|
+
<!-- Success Rate Bar -->
|
43
|
+
<div class="w-16 bg-gray-200 dark:bg-gray-600 rounded-full h-1.5">
|
44
|
+
<div class="bg-green-600 h-1.5 rounded-full transition-all duration-300"
|
45
|
+
style="width: {{ stat.success_rate|floatformat:0 }}%"></div>
|
46
|
+
</div>
|
47
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
48
|
+
{{ stat.success_rate|floatformat:0 }}%
|
49
|
+
</span>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</div>
|
53
|
+
{% empty %}
|
54
|
+
<div class="text-center py-8">
|
55
|
+
<span class="material-icons-outlined text-gray-400 text-4xl mb-2">bar_chart</span>
|
56
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">No Provider Data</p>
|
57
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Provider statistics will appear here once payments are processed</p>
|
58
|
+
</div>
|
59
|
+
{% endfor %}
|
60
|
+
</div>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Payment Status Badge Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/status_badge.html' with payment=payment %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment: UniversalPayment object
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<span class="payment-status-badge inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
12
|
+
{% if payment.status == 'pending' %}bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400
|
13
|
+
{% elif payment.status == 'confirming' %}bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400
|
14
|
+
{% elif payment.status == 'confirmed' or payment.status == 'completed' %}bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400
|
15
|
+
{% elif payment.status == 'failed' %}bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400
|
16
|
+
{% elif payment.status == 'expired' or payment.status == 'cancelled' %}bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400
|
17
|
+
{% elif payment.status == 'refunded' %}bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400
|
18
|
+
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400{% endif %}"
|
19
|
+
data-status="{{ payment.status }}">
|
20
|
+
{% if payment.status == 'pending' %}
|
21
|
+
<span class="material-icons-outlined text-sm mr-1 animate-pulse">pending</span>
|
22
|
+
{% elif payment.status == 'confirming' %}
|
23
|
+
<span class="material-icons-outlined text-sm mr-1 animate-spin">sync</span>
|
24
|
+
{% elif payment.status == 'confirmed' %}
|
25
|
+
<span class="material-icons-outlined text-sm mr-1">check_circle</span>
|
26
|
+
{% elif payment.status == 'completed' %}
|
27
|
+
<span class="material-icons-outlined text-sm mr-1">verified</span>
|
28
|
+
{% elif payment.status == 'failed' %}
|
29
|
+
<span class="material-icons-outlined text-sm mr-1">error</span>
|
30
|
+
{% elif payment.status == 'expired' %}
|
31
|
+
<span class="material-icons-outlined text-sm mr-1">schedule</span>
|
32
|
+
{% elif payment.status == 'cancelled' %}
|
33
|
+
<span class="material-icons-outlined text-sm mr-1">cancel</span>
|
34
|
+
{% elif payment.status == 'refunded' %}
|
35
|
+
<span class="material-icons-outlined text-sm mr-1">undo</span>
|
36
|
+
{% else %}
|
37
|
+
<span class="material-icons-outlined text-sm mr-1">help</span>
|
38
|
+
{% endif %}
|
39
|
+
|
40
|
+
<span class="status-text">{{ payment.get_status_display }}</span>
|
41
|
+
</span>
|