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.
Files changed (246) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +269 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +183 -460
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  48. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  49. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  50. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  51. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  52. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  53. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  54. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
  55. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  56. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  57. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
  58. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  59. django_cfg/apps/payments/config/__init__.py +14 -15
  60. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  61. django_cfg/apps/payments/config/helpers.py +8 -13
  62. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  63. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  64. django_cfg/apps/payments/middleware/api_access.py +32 -6
  65. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  66. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  67. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  68. django_cfg/apps/payments/models/balance.py +12 -0
  69. django_cfg/apps/payments/models/currencies.py +106 -32
  70. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  71. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  72. django_cfg/apps/payments/models/payments.py +94 -0
  73. django_cfg/apps/payments/services/core/base.py +4 -4
  74. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  75. django_cfg/apps/payments/services/core/payment_service.py +266 -39
  76. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  77. django_cfg/apps/payments/services/providers/base.py +303 -41
  78. django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
  79. django_cfg/apps/payments/services/providers/models/base.py +145 -0
  80. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  81. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  82. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  83. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  84. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  85. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  86. django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
  87. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  88. django_cfg/apps/payments/services/providers/registry.py +9 -37
  89. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  90. django_cfg/apps/payments/services/types/requests.py +19 -7
  91. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  92. django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
  93. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  94. django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
  95. django_cfg/apps/payments/tasks/__init__.py +39 -0
  96. django_cfg/apps/payments/tasks/types.py +73 -0
  97. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  98. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  99. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  100. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  101. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  102. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  103. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  104. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  105. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  106. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  107. django_cfg/apps/payments/urls.py +3 -2
  108. django_cfg/apps/payments/urls_admin.py +1 -1
  109. django_cfg/apps/payments/views/api/currencies.py +8 -5
  110. django_cfg/apps/payments/views/overview/services.py +2 -2
  111. django_cfg/apps/payments/views/serializers/currencies.py +22 -8
  112. django_cfg/apps/support/admin/__init__.py +10 -1
  113. django_cfg/apps/support/admin/support_admin.py +338 -141
  114. django_cfg/apps/tasks/admin/__init__.py +11 -0
  115. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  116. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  117. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  118. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  119. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  120. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  121. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  122. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  123. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  124. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  125. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  126. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  127. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  128. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  129. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  130. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  131. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  132. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  133. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  134. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  135. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  136. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  137. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  138. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  139. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  140. django_cfg/apps/tasks/urls.py +2 -2
  141. django_cfg/apps/tasks/urls_admin.py +2 -2
  142. django_cfg/apps/tasks/utils/__init__.py +1 -0
  143. django_cfg/apps/tasks/utils/simulator.py +356 -0
  144. django_cfg/apps/tasks/views/__init__.py +16 -0
  145. django_cfg/apps/tasks/views/api.py +569 -0
  146. django_cfg/apps/tasks/views/dashboard.py +58 -0
  147. django_cfg/config.py +1 -1
  148. django_cfg/core/config.py +10 -5
  149. django_cfg/core/generation.py +1 -1
  150. django_cfg/core/integration/__init__.py +21 -0
  151. django_cfg/management/commands/__init__.py +13 -1
  152. django_cfg/management/commands/migrate_all.py +9 -3
  153. django_cfg/management/commands/migrator.py +11 -6
  154. django_cfg/management/commands/rundramatiq.py +3 -2
  155. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  156. django_cfg/middleware/__init__.py +0 -2
  157. django_cfg/models/api_keys.py +115 -0
  158. django_cfg/models/constance.py +0 -11
  159. django_cfg/models/payments.py +137 -3
  160. django_cfg/modules/django_admin/__init__.py +64 -0
  161. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  162. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  163. django_cfg/modules/django_admin/decorators/display.py +106 -0
  164. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  165. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  166. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  167. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  168. django_cfg/modules/django_admin/models/__init__.py +20 -0
  169. django_cfg/modules/django_admin/models/action_models.py +33 -0
  170. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  171. django_cfg/modules/django_admin/models/base.py +26 -0
  172. django_cfg/modules/django_admin/models/display_models.py +31 -0
  173. django_cfg/modules/django_admin/utils/badges.py +159 -0
  174. django_cfg/modules/django_admin/utils/displays.py +247 -0
  175. django_cfg/modules/django_currency/__init__.py +2 -2
  176. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  177. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  178. django_cfg/modules/django_currency/core/converter.py +12 -12
  179. django_cfg/modules/django_currency/database/__init__.py +2 -2
  180. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  181. django_cfg/modules/django_llm/llm/client.py +10 -2
  182. django_cfg/modules/django_tasks.py +54 -21
  183. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  184. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  185. django_cfg/modules/django_unfold/dashboard.py +14 -13
  186. django_cfg/modules/django_unfold/models/config.py +1 -1
  187. django_cfg/registry/core.py +7 -9
  188. django_cfg/registry/third_party.py +2 -2
  189. django_cfg/template_archive/django_sample.zip +0 -0
  190. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
  191. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
  192. django_cfg/apps/accounts/admin/activity.py +0 -96
  193. django_cfg/apps/accounts/admin/group.py +0 -17
  194. django_cfg/apps/accounts/admin/otp.py +0 -59
  195. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  196. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  197. django_cfg/apps/accounts/admin/user.py +0 -300
  198. django_cfg/apps/agents/core/agent.py +0 -281
  199. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  200. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  201. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  202. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  203. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  204. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  205. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  206. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  207. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  208. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  209. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  210. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  211. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  212. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  213. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  214. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  215. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  216. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  217. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  218. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  219. django_cfg/apps/payments/config/constance/fields.py +0 -69
  220. django_cfg/apps/payments/config/constance/settings.py +0 -160
  221. django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
  222. django_cfg/apps/tasks/admin.py +0 -320
  223. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  224. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  225. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  226. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  227. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  228. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  229. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  230. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  231. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  232. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  233. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  234. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  235. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  236. django_cfg/apps/tasks/views.py +0 -461
  237. django_cfg/management/commands/auto_generate.py +0 -486
  238. django_cfg/middleware/static_nocache.py +0 -55
  239. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  240. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  241. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  242. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  243. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  244. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,363 @@
1
+ {% extends 'payments/base.html' %}
2
+ {% load static %}
3
+ {% load payment_tags %}
4
+
5
+ {% block title %}Payment Details - Payment Admin{% endblock %}
6
+
7
+ {% block page_title %}Payment Details{% endblock %}
8
+ {% block page_subtitle %}Payment {{ payment.internal_payment_id|default:payment.id|truncatechars:8 }}{% endblock %}
9
+
10
+ {% block content %}
11
+ <div x-data="paymentDetail()" x-init="init('{{ payment.id }}')" class="space-y-6">
12
+
13
+ <!-- Back Navigation -->
14
+ <div class="flex items-center space-x-4 mb-6">
15
+ <a href="{% url 'cfg_payments_admin:payment-list' %}"
16
+ class="inline-flex items-center px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md text-sm font-medium transition-colors">
17
+ <span class="material-icons-outlined mr-2">arrow_back</span>
18
+ Back to Payments
19
+ </a>
20
+
21
+ <!-- Status Refresh Button -->
22
+ <button @click="refreshPaymentStatus()"
23
+ :disabled="loading"
24
+ class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md text-sm font-medium transition-colors">
25
+ <span class="material-icons-outlined mr-2" :class="{ 'animate-spin': loading }">refresh</span>
26
+ <span x-show="!loading">Refresh Status</span>
27
+ <span x-show="loading">Refreshing...</span>
28
+ </button>
29
+ </div>
30
+
31
+ <!-- Payment Overview Card -->
32
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg">
33
+ <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
34
+ <div class="flex items-center justify-between">
35
+ <div>
36
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">Payment Overview</h3>
37
+ <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Complete payment information</p>
38
+ </div>
39
+ <div class="flex items-center space-x-3">
40
+ {% include 'payments/components/status_badge.html' with payment=payment %}
41
+ {% if payment.pay_address %}
42
+ <button @click="showQRCode = !showQRCode"
43
+ class="inline-flex items-center px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium transition-colors">
44
+ <span class="material-icons-outlined mr-2">qr_code</span>
45
+ <span x-text="showQRCode ? 'Hide QR' : 'Show QR'"></span>
46
+ </button>
47
+ {% endif %}
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="p-6">
53
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
54
+ <!-- Payment Information -->
55
+ <div>
56
+ <h4 class="text-md font-semibold text-gray-900 dark:text-white mb-4">Payment Information</h4>
57
+ <dl class="space-y-3">
58
+ <div class="flex justify-between">
59
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Internal ID:</dt>
60
+ <dd class="text-sm font-mono text-gray-900 dark:text-white">{{ payment.internal_payment_id|default:payment.id }}</dd>
61
+ </div>
62
+ <div class="flex justify-between">
63
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Provider ID:</dt>
64
+ <dd class="text-sm font-mono text-gray-900 dark:text-white">{{ payment.provider_payment_id|default:"N/A" }}</dd>
65
+ </div>
66
+ <div class="flex justify-between">
67
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Amount:</dt>
68
+ <dd class="text-sm font-semibold text-gray-900 dark:text-white">${{ payment.amount_usd|floatformat:2 }}</dd>
69
+ </div>
70
+ <div class="flex justify-between">
71
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Currency:</dt>
72
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.currency.code }} - {{ payment.currency.name }}</dd>
73
+ </div>
74
+ <div class="flex justify-between">
75
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Provider:</dt>
76
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.provider }}</dd>
77
+ </div>
78
+ {% if payment.description %}
79
+ <div class="flex justify-between">
80
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Description:</dt>
81
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.description }}</dd>
82
+ </div>
83
+ {% endif %}
84
+ {% if payment.payment_url %}
85
+ <div class="flex justify-between">
86
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Payment URL:</dt>
87
+ <dd class="text-sm">
88
+ <a href="{{ payment.payment_url }}" target="_blank"
89
+ class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">
90
+ Open Payment Page
91
+ <span class="material-icons-outlined text-sm ml-1">open_in_new</span>
92
+ </a>
93
+ </dd>
94
+ </div>
95
+ {% endif %}
96
+ </dl>
97
+ </div>
98
+
99
+ <!-- User Information -->
100
+ <div>
101
+ <h4 class="text-md font-semibold text-gray-900 dark:text-white mb-4">User Information</h4>
102
+ <dl class="space-y-3">
103
+ <div class="flex justify-between">
104
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Name:</dt>
105
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.user.get_full_name|default:"N/A" }}</dd>
106
+ </div>
107
+ <div class="flex justify-between">
108
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Email:</dt>
109
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.user.email }}</dd>
110
+ </div>
111
+ <div class="flex justify-between">
112
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Username:</dt>
113
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.user.username }}</dd>
114
+ </div>
115
+ <div class="flex justify-between">
116
+ <dt class="text-sm text-gray-600 dark:text-gray-400">User ID:</dt>
117
+ <dd class="text-sm font-mono text-gray-900 dark:text-white">{{ payment.user.id }}</dd>
118
+ </div>
119
+ </dl>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+
126
+ <!-- QR Code Section (Collapsible) -->
127
+ {% if payment.pay_address %}
128
+ <div x-show="showQRCode"
129
+ x-transition:enter="transition ease-out duration-300"
130
+ x-transition:enter-start="opacity-0 transform scale-95"
131
+ x-transition:enter-end="opacity-100 transform scale-100"
132
+ x-transition:leave="transition ease-in duration-200"
133
+ x-transition:leave-start="opacity-100 transform scale-100"
134
+ x-transition:leave-end="opacity-0 transform scale-95"
135
+ class="bg-white dark:bg-gray-800 shadow rounded-lg">
136
+ <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
137
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">Payment QR Code</h3>
138
+ <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Scan to make payment</p>
139
+ </div>
140
+
141
+ <div class="p-6">
142
+ {% include 'payments/components/payment_qr_code.html' %}
143
+ </div>
144
+ </div>
145
+ {% else %}
146
+ <div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded mb-4">
147
+ <strong>QR Code not shown:</strong> payment.pay_address is empty or null
148
+ </div>
149
+ {% endif %}
150
+
151
+ <!-- Crypto Details (if applicable) -->
152
+ {% if payment.pay_address %}
153
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg">
154
+ <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
155
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">Cryptocurrency Details</h3>
156
+ <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Blockchain transaction information</p>
157
+ </div>
158
+
159
+ <div class="p-6">
160
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
161
+ <!-- Payment Addresses -->
162
+ <div>
163
+ <h4 class="text-md font-semibold text-gray-900 dark:text-white mb-4">Payment Addresses</h4>
164
+ <dl class="space-y-3">
165
+ <div>
166
+ <dt class="text-sm text-gray-600 dark:text-gray-400 mb-1">Pay Address:</dt>
167
+ <dd class="text-sm font-mono bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600 break-all" data-field="pay_address">
168
+ {{ payment.pay_address }}
169
+ </dd>
170
+ </div>
171
+ {% if payment.sender_address %}
172
+ <div>
173
+ <dt class="text-sm text-gray-600 dark:text-gray-400 mb-1">Sender Address:</dt>
174
+ <dd class="text-sm font-mono bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600 break-all">
175
+ {{ payment.sender_address }}
176
+ </dd>
177
+ </div>
178
+ {% endif %}
179
+ {% if payment.receiver_address %}
180
+ <div>
181
+ <dt class="text-sm text-gray-600 dark:text-gray-400 mb-1">Receiver Address:</dt>
182
+ <dd class="text-sm font-mono bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600 break-all">
183
+ {{ payment.receiver_address }}
184
+ </dd>
185
+ </div>
186
+ {% endif %}
187
+ </dl>
188
+ </div>
189
+
190
+ <!-- Transaction Details -->
191
+ <div>
192
+ <h4 class="text-md font-semibold text-gray-900 dark:text-white mb-4">Transaction Details</h4>
193
+ <dl class="space-y-3">
194
+ {% if payment.pay_amount %}
195
+ <div class="flex justify-between">
196
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Pay Amount:</dt>
197
+ <dd class="text-sm font-semibold text-gray-900 dark:text-white" data-field="pay_amount">
198
+ {{ payment.pay_amount|floatformat:8 }} {{ payment.currency.code }}
199
+ </dd>
200
+ </div>
201
+ {% endif %}
202
+ {% if payment.network %}
203
+ <div class="flex justify-between">
204
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Network:</dt>
205
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.network.name }}</dd>
206
+ </div>
207
+ {% endif %}
208
+ {% if payment.expires_at %}
209
+ <div class="flex justify-between">
210
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Expires At:</dt>
211
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.expires_at|date:"M d, Y H:i" }}</dd>
212
+ </div>
213
+ {% endif %}
214
+ {% if payment.completed_at %}
215
+ <div class="flex justify-between">
216
+ <dt class="text-sm text-gray-600 dark:text-gray-400">Completed At:</dt>
217
+ <dd class="text-sm text-gray-900 dark:text-white">{{ payment.completed_at|date:"M d, Y H:i" }}</dd>
218
+ </div>
219
+ {% endif %}
220
+ {% if payment.transaction_hash %}
221
+ <div>
222
+ <dt class="text-sm text-gray-600 dark:text-gray-400 mb-1">Transaction Hash:</dt>
223
+ <dd class="text-sm font-mono bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600 break-all" data-field="transaction_hash">
224
+ {{ payment.transaction_hash }}
225
+ {% if payment.get_payment_explorer_link %}
226
+ <a href="{{ payment.get_payment_explorer_link }}" target="_blank"
227
+ class="ml-2 text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">
228
+ <span class="material-icons-outlined text-sm">open_in_new</span>
229
+ </a>
230
+ {% endif %}
231
+ </dd>
232
+ </div>
233
+ {% endif %}
234
+ </dl>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ {% endif %}
240
+
241
+ <!-- Timeline -->
242
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg">
243
+ <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
244
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">Timeline</h3>
245
+ <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Payment lifecycle timestamps</p>
246
+ </div>
247
+
248
+ <div class="p-6">
249
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6">
250
+ <div class="text-center">
251
+ <div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Created</div>
252
+ <div class="text-sm font-medium text-gray-900 dark:text-white">{{ payment.created_at|date:"M d, Y" }}</div>
253
+ <div class="text-xs text-gray-500 dark:text-gray-400">{{ payment.created_at|time:"H:i:s" }}</div>
254
+ </div>
255
+ <div class="text-center">
256
+ <div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Status Changed</div>
257
+ {% if payment.status_changed_at %}
258
+ <div class="text-sm font-medium text-gray-900 dark:text-white" data-field="status_changed_at">{{ payment.status_changed_at|date:"M d, Y" }}</div>
259
+ <div class="text-xs text-gray-500 dark:text-gray-400">{{ payment.status_changed_at|time:"H:i:s" }}</div>
260
+ {% else %}
261
+ <div class="text-sm text-gray-500 dark:text-gray-400" data-field="status_changed_at">Not changed</div>
262
+ {% endif %}
263
+ </div>
264
+ <div class="text-center">
265
+ <div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Updated</div>
266
+ <div class="text-sm font-medium text-gray-900 dark:text-white">{{ payment.updated_at|date:"M d, Y" }}</div>
267
+ <div class="text-xs text-gray-500 dark:text-gray-400">{{ payment.updated_at|time:"H:i:s" }}</div>
268
+ </div>
269
+ <div class="text-center">
270
+ <div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Processed</div>
271
+ {% if payment.processed_at %}
272
+ <div class="text-sm font-medium text-gray-900 dark:text-white">{{ payment.processed_at|date:"M d, Y" }}</div>
273
+ <div class="text-xs text-gray-500 dark:text-gray-400">{{ payment.processed_at|time:"H:i:s" }}</div>
274
+ {% else %}
275
+ <div class="text-sm text-gray-500 dark:text-gray-400">Not processed</div>
276
+ {% endif %}
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+
282
+ <!-- Provider Data (if available) -->
283
+ {% if payment.provider_data %}
284
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg">
285
+ <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
286
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white">Provider Information</h3>
287
+ <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">Data provided by {{ payment.provider|title }}</p>
288
+ </div>
289
+
290
+ <div class="p-6">
291
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
292
+ {% for key, value in payment.provider_data.items %}
293
+ <div>
294
+ <dt class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-1">{{ key|title }}:</dt>
295
+ <dd class="text-sm text-gray-900 dark:text-white">
296
+ {% if value|length > 50 %}
297
+ <div class="font-mono bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600 break-all text-xs">
298
+ {{ value }}
299
+ </div>
300
+ {% else %}
301
+ {{ value }}
302
+ {% endif %}
303
+ </dd>
304
+ </div>
305
+ {% endfor %}
306
+ </div>
307
+
308
+ <!-- Raw JSON (collapsible) -->
309
+ <div class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
310
+ <button onclick="document.getElementById('raw-provider-data').classList.toggle('hidden')"
311
+ class="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">
312
+ <span class="material-icons-outlined text-sm mr-1">code</span>
313
+ Toggle Raw JSON
314
+ </button>
315
+ <div id="raw-provider-data" class="hidden mt-3">
316
+ <pre class="text-xs font-mono bg-gray-50 dark:bg-gray-700 p-4 rounded border border-gray-200 dark:border-gray-600 overflow-x-auto">{{ payment.provider_data|pprint }}</pre>
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ {% endif %}
322
+
323
+ <!-- Actions -->
324
+ <div class="bg-white dark:bg-gray-800 shadow rounded-lg">
325
+ <div class="p-6">
326
+ <div class="flex flex-wrap gap-3">
327
+ {% if payment.status == 'pending' or payment.status == 'confirming' %}
328
+ <button @click="cancelPayment()"
329
+ :disabled="loading"
330
+ class="inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-red-400 text-white rounded-md text-sm font-medium transition-colors">
331
+ <span class="material-icons-outlined mr-2">cancel</span>
332
+ Cancel Payment
333
+ </button>
334
+ {% endif %}
335
+
336
+ <button @click="exportDetails()"
337
+ class="inline-flex items-center px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md text-sm font-medium transition-colors">
338
+ <span class="material-icons-outlined mr-2">file_download</span>
339
+ Export Details
340
+ </button>
341
+
342
+ {% if payment.callback_url %}
343
+ <a href="{{ payment.callback_url }}"
344
+ target="_blank"
345
+ class="inline-flex items-center px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md text-sm font-medium transition-colors">
346
+ <span class="material-icons-outlined mr-2">open_in_new</span>
347
+ Callback URL
348
+ </a>
349
+ {% endif %}
350
+ </div>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ {% endblock %}
355
+
356
+ {% block extra_js %}
357
+ <!-- Payment API Client -->
358
+ <script src="{% static 'payments/js/api-client.js' %}"></script>
359
+ <!-- Payment Detail Page JavaScript -->
360
+ <script src="{% static 'payments/js/payment-detail.js' %}"></script>
361
+ <!-- QR Code Library -->
362
+ <script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>
363
+ {% endblock %}
@@ -18,6 +18,36 @@
18
18
  <form @submit.prevent="submitForm()" class="p-6 space-y-6">
19
19
  {% csrf_token %}
20
20
 
21
+ <!-- Error Display -->
22
+ <div x-show="errorMessage"
23
+ x-transition:enter="transition ease-out duration-300"
24
+ x-transition:enter-start="opacity-0 transform -translate-y-2"
25
+ x-transition:enter-end="opacity-100 transform translate-y-0"
26
+ x-transition:leave="transition ease-in duration-200"
27
+ x-transition:leave-start="opacity-100 transform translate-y-0"
28
+ x-transition:leave-end="opacity-0 transform -translate-y-2"
29
+ class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-4">
30
+ <div class="flex">
31
+ <div class="flex-shrink-0">
32
+ <span class="material-icons-outlined text-red-400">error</span>
33
+ </div>
34
+ <div class="ml-3">
35
+ <h3 class="text-sm font-medium text-red-800 dark:text-red-200">
36
+ Payment Creation Failed
37
+ </h3>
38
+ <div class="mt-2 text-sm text-red-700 dark:text-red-300">
39
+ <p x-text="errorMessage"></p>
40
+ </div>
41
+ <div class="mt-3">
42
+ <button @click="clearError()"
43
+ class="text-sm font-medium text-red-800 dark:text-red-200 hover:text-red-600 dark:hover:text-red-100">
44
+ Dismiss
45
+ </button>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
21
51
  <!-- Amount -->
22
52
  <div>
23
53
  <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
@@ -96,12 +126,8 @@
96
126
  class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white disabled:opacity-50"
97
127
  required>
98
128
  <option value="">Select currency...</option>
99
- <template x-for="currency in currencies" :key="currency.code">
100
- <option :value="currency.code">
101
- <span x-text="currency.code"></span> -
102
- <span x-text="currency.name"></span>
103
- <span x-show="currency.type === 'crypto'" class="text-blue-600"> (Crypto)</span>
104
- <span x-show="currency.network_name" class="text-gray-500" x-text="`[${currency.network_name}]`"></span>
129
+ <template x-for="currency in currencies" :key="`${currency.provider_currency_code || currency.code}-${currency.network?.code || Math.random()}`">
130
+ <option :value="currency.provider_currency_code || currency.code" x-text="`${currency.currency?.code || currency.code} - ${currency.currency?.name || currency.name}${currency.currency?.currency_type === 'crypto' ? ' (Crypto)' : ''}${currency.network?.name ? ' [' + currency.network.name + ']' : ''}`">
105
131
  </option>
106
132
  </template>
107
133
  </select>
@@ -122,22 +148,22 @@
122
148
  <span class="text-gray-500">Network:</span>
123
149
  <span x-text="getCurrencyInfo(form.currency_code)?.network_name"></span>
124
150
  </div>
125
- <div x-show="getCurrencyInfo(form.currency_code)?.min_amount || getCurrencyInfo(form.currency_code)?.max_amount" class="mt-1">
151
+ <div x-show="getCurrencyInfo(form.currency_code)?.provider_min_amount_usd || getCurrencyInfo(form.currency_code)?.provider_max_amount_usd" class="mt-1">
126
152
  <span class="text-gray-500">Limits:</span>
127
- <span x-show="getCurrencyInfo(form.currency_code)?.min_amount">
128
- Min: $<span x-text="getCurrencyInfo(form.currency_code)?.min_amount"></span>
153
+ <span x-show="getCurrencyInfo(form.currency_code)?.provider_min_amount_usd">
154
+ Min: $<span x-text="getCurrencyInfo(form.currency_code)?.provider_min_amount_usd"></span>
129
155
  </span>
130
- <span x-show="getCurrencyInfo(form.currency_code)?.max_amount">
131
- Max: $<span x-text="getCurrencyInfo(form.currency_code)?.max_amount"></span>
156
+ <span x-show="getCurrencyInfo(form.currency_code)?.provider_max_amount_usd">
157
+ Max: $<span x-text="getCurrencyInfo(form.currency_code)?.provider_max_amount_usd"></span>
132
158
  </span>
133
159
  </div>
134
- <div x-show="getCurrencyInfo(form.currency_code)?.fee_percentage > 0 || getCurrencyInfo(form.currency_code)?.fixed_fee > 0" class="mt-1">
160
+ <div x-show="getCurrencyInfo(form.currency_code)?.provider_fee_percentage > 0 || getCurrencyInfo(form.currency_code)?.provider_fixed_fee_usd > 0" class="mt-1">
135
161
  <span class="text-gray-500">Fees:</span>
136
- <span x-show="getCurrencyInfo(form.currency_code)?.fee_percentage > 0">
137
- <span x-text="(getCurrencyInfo(form.currency_code)?.fee_percentage * 100).toFixed(2)"></span>%
162
+ <span x-show="getCurrencyInfo(form.currency_code)?.provider_fee_percentage > 0">
163
+ <span x-text="(getCurrencyInfo(form.currency_code)?.provider_fee_percentage * 100).toFixed(2)"></span>%
138
164
  </span>
139
- <span x-show="getCurrencyInfo(form.currency_code)?.fixed_fee > 0">
140
- + $<span x-text="getCurrencyInfo(form.currency_code)?.fixed_fee"></span>
165
+ <span x-show="getCurrencyInfo(form.currency_code)?.provider_fixed_fee_usd > 0">
166
+ + $<span x-text="getCurrencyInfo(form.currency_code)?.provider_fixed_fee_usd"></span>
141
167
  </span>
142
168
  </div>
143
169
  </div>
@@ -187,7 +213,7 @@
187
213
  Cancel
188
214
  </a>
189
215
  <button type="submit"
190
- :disabled="loading || !form.amount || !form.currency || !form.provider"
216
+ :disabled="loading || !form.amount_usd || !form.currency_code || !form.provider || !form.user"
191
217
  class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed">
192
218
  <span x-show="!loading">Create Payment</span>
193
219
  <span x-show="loading">Creating...</span>
@@ -13,6 +13,7 @@ from .api import (
13
13
  AdminPaymentViewSet,
14
14
  AdminWebhookViewSet,
15
15
  AdminWebhookEventViewSet,
16
+ WebhookTestViewSet,
16
17
  AdminStatsViewSet,
17
18
  AdminUserViewSet,
18
19
  )
@@ -29,6 +30,7 @@ __all__ = [
29
30
  'AdminPaymentViewSet',
30
31
  'AdminWebhookViewSet',
31
32
  'AdminWebhookEventViewSet',
33
+ 'WebhookTestViewSet',
32
34
  'AdminStatsViewSet',
33
35
  'AdminUserViewSet',
34
36
  ]
@@ -7,6 +7,7 @@ DRF ViewSets for payment management in admin interface.
7
7
  from rest_framework import viewsets, status
8
8
  from rest_framework.decorators import action
9
9
  from rest_framework.response import Response
10
+ from rest_framework import serializers
10
11
  from django_filters.rest_framework import DjangoFilterBackend
11
12
  from rest_framework.filters import SearchFilter, OrderingFilter
12
13
  from django.db.models import Count, Sum, Avg, Q
@@ -72,6 +73,53 @@ class AdminPaymentViewSet(AdminBaseViewSet):
72
73
 
73
74
  return queryset
74
75
 
76
+ def create(self, request, *args, **kwargs):
77
+ """Create payment with enhanced error handling."""
78
+ serializer = self.get_serializer(data=request.data)
79
+
80
+ if serializer.is_valid():
81
+ try:
82
+ payment = serializer.save()
83
+ response_serializer = AdminPaymentDetailSerializer(payment, context={'request': request})
84
+ return Response(response_serializer.data, status=status.HTTP_201_CREATED)
85
+ except serializers.ValidationError as e:
86
+ # Extract the error message from ValidationError
87
+ error_message = str(e)
88
+ if hasattr(e, 'detail') and isinstance(e.detail, list) and len(e.detail) > 0:
89
+ error_message = str(e.detail[0])
90
+ elif hasattr(e, 'detail') and isinstance(e.detail, dict):
91
+ # Handle field-specific errors
92
+ error_message = '; '.join([f"{field}: {', '.join(errors) if isinstance(errors, list) else errors}"
93
+ for field, errors in e.detail.items()])
94
+
95
+ return Response(
96
+ {
97
+ 'success': False,
98
+ 'error': error_message,
99
+ 'message': error_message # Add message field for frontend compatibility
100
+ },
101
+ status=status.HTTP_400_BAD_REQUEST
102
+ )
103
+ else:
104
+ # Handle validation errors
105
+ error_messages = []
106
+ for field, errors in serializer.errors.items():
107
+ if isinstance(errors, list):
108
+ error_messages.extend([str(error) for error in errors])
109
+ else:
110
+ error_messages.append(str(errors))
111
+
112
+ error_message = '; '.join(error_messages)
113
+ return Response(
114
+ {
115
+ 'success': False,
116
+ 'error': error_message,
117
+ 'message': error_message, # Add message field for frontend compatibility
118
+ 'errors': serializer.errors
119
+ },
120
+ status=status.HTTP_400_BAD_REQUEST
121
+ )
122
+
75
123
  @action(detail=False, methods=['get'])
76
124
  def stats(self, request):
77
125
  """Get comprehensive payment statistics."""
@@ -189,3 +237,57 @@ class AdminPaymentViewSet(AdminBaseViewSet):
189
237
 
190
238
  serializer = self.get_serializer(payment)
191
239
  return Response(serializer.data)
240
+
241
+ @action(detail=True, methods=['post'])
242
+ def refresh_status(self, request, pk=None):
243
+ """Refresh payment status from provider via AJAX."""
244
+ payment = self.get_object()
245
+
246
+ try:
247
+ # Import here to avoid circular imports
248
+ from django_cfg.apps.payments.services.core.payment_service import PaymentService
249
+
250
+ # Create PaymentStatusRequest
251
+ from django_cfg.apps.payments.services.types import PaymentStatusRequest
252
+
253
+ status_request = PaymentStatusRequest(
254
+ payment_id=str(payment.id),
255
+ force_provider_check=True
256
+ )
257
+
258
+ # Create service instance and force refresh from provider
259
+ payment_service = PaymentService()
260
+ result = payment_service.get_payment_status(status_request)
261
+
262
+ if result.success:
263
+ # Reload payment from database to get updated data
264
+ payment.refresh_from_db()
265
+
266
+ # Serialize updated payment data
267
+ serializer = self.get_serializer(payment)
268
+
269
+ return Response({
270
+ 'success': True,
271
+ 'message': 'Payment status refreshed successfully',
272
+ 'payment': serializer.data,
273
+ 'provider_response': result.data.get('provider_response') if result.data else None
274
+ })
275
+ else:
276
+ return Response({
277
+ 'success': False,
278
+ 'message': result.message or 'Failed to refresh payment status',
279
+ 'error_details': result.data if result.data else None
280
+ }, status=status.HTTP_400_BAD_REQUEST)
281
+
282
+ except Exception as e:
283
+ logger.error(f"Error refreshing payment {payment.id} status", extra={
284
+ 'payment_id': payment.id,
285
+ 'error': str(e),
286
+ 'admin_user': request.user.id
287
+ })
288
+
289
+ return Response({
290
+ 'success': False,
291
+ 'message': f'Error refreshing payment status: {str(e)}',
292
+ 'error_type': type(e).__name__
293
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)