django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,235 +1,408 @@
1
1
  """
2
- Universal payment provider management command.
2
+ Provider management command for Universal Payment System v2.0.
3
3
 
4
- Combines sync_providers functionality with additional features.
5
-
6
- Usage:
7
- python manage.py manage_providers # Sync all active providers
8
- python manage.py manage_providers --provider nowpayments # Sync specific provider
9
- python manage.py manage_providers --with-rates # Sync providers + update USD rates
10
- python manage.py manage_providers --stats # Show provider statistics
4
+ Manages payment provider synchronization, health checks, and statistics.
11
5
  """
12
6
 
13
7
  from django.core.management.base import BaseCommand, CommandError
14
8
  from django.db import transaction
15
9
  from django.utils import timezone
16
- from typing import List, Optional
10
+ from datetime import timedelta
11
+ from typing import List, Optional, Dict
17
12
  import time
18
13
 
19
14
  from django_cfg.modules.django_logger import get_logger
20
- from django_cfg.apps.payments.services.providers.registry import get_payment_provider, get_available_providers
21
- from django_cfg.apps.payments.models import Currency, ProviderCurrency
15
+ from django_cfg.apps.payments.models import Currency, Network, ProviderCurrency
16
+ from django_cfg.apps.payments.services.providers import get_provider_registry
22
17
 
23
18
  logger = get_logger("manage_providers")
24
19
 
25
20
 
26
21
  class Command(BaseCommand):
27
- """Universal payment provider management command."""
22
+ """
23
+ Universal provider management command.
24
+
25
+ Features:
26
+ - Provider currency synchronization
27
+ - Health monitoring and statistics
28
+ - Selective provider updates
29
+ - Integration with ProviderRegistry
30
+ """
28
31
 
29
- help = 'Manage payment providers: sync currencies, networks, and rates'
32
+ help = 'Manage payment providers and their currencies'
30
33
 
31
34
  def add_arguments(self, parser):
32
- """Add command line arguments."""
35
+ """Add command arguments."""
36
+
37
+ # Main operation modes
38
+ parser.add_argument(
39
+ '--all',
40
+ action='store_true',
41
+ help='Sync all active providers'
42
+ )
43
+
33
44
  parser.add_argument(
34
45
  '--provider',
35
46
  type=str,
36
- help='Specific provider(s) to sync (comma-separated). E.g: nowpayments,cryptomus'
47
+ help='Sync specific provider (e.g., nowpayments, cryptomus)'
48
+ )
49
+
50
+ parser.add_argument(
51
+ '--health-check',
52
+ action='store_true',
53
+ help='Perform health check on all providers'
37
54
  )
55
+
38
56
  parser.add_argument(
39
- '--all',
57
+ '--stats',
40
58
  action='store_true',
41
- help='Sync all available providers'
59
+ help='Show provider statistics'
42
60
  )
61
+
62
+ # Sync options
43
63
  parser.add_argument(
44
64
  '--with-rates',
45
65
  action='store_true',
46
- help='Also update USD exchange rates after sync'
66
+ help='Update USD rates after syncing currencies'
47
67
  )
68
+
48
69
  parser.add_argument(
49
- '--stats',
70
+ '--currencies',
71
+ type=str,
72
+ help='Comma-separated list of currency codes to sync (e.g., BTC,ETH,USDT)'
73
+ )
74
+
75
+ parser.add_argument(
76
+ '--force-refresh',
50
77
  action='store_true',
51
- help='Show provider statistics'
78
+ help='Force refresh even if recently synced'
52
79
  )
80
+
81
+ # Behavior options
53
82
  parser.add_argument(
54
83
  '--dry-run',
55
84
  action='store_true',
56
- help='Show what would be synced without making changes'
85
+ help='Show what would be done without making changes'
57
86
  )
87
+
58
88
  parser.add_argument(
59
89
  '--verbose',
60
90
  action='store_true',
61
- help='Show detailed progress information'
91
+ help='Show detailed output'
62
92
  )
63
-
93
+
64
94
  def handle(self, *args, **options):
65
- """Execute the command."""
66
- start_time = time.time()
95
+ """Main command handler."""
67
96
 
68
- self.stdout.write('=' * 60)
69
- self.stdout.write(self.style.SUCCESS('šŸš€ Provider Management Tool'))
70
- self.stdout.write('=' * 60)
97
+ start_time = time.time()
71
98
 
72
- if options['stats']:
73
- return self._show_stats()
99
+ try:
100
+ self.stdout.write(
101
+ self.style.SUCCESS('šŸš€ Starting Provider Management')
102
+ )
74
103
 
75
- # Determine which providers to sync
76
- if options['provider']:
77
- provider_names = [p.strip() for p in options['provider'].split(',')]
78
- elif options['all']:
79
- provider_names = get_available_providers()
80
- else:
81
- # Default: sync active providers only
82
- provider_names = get_available_providers()
104
+ # Get provider registry
105
+ registry = get_provider_registry()
83
106
 
84
- # Sync providers
85
- total_synced = 0
86
- for provider_name in provider_names:
87
- synced = self._sync_provider(provider_name, options)
88
- total_synced += synced
107
+ # Determine operation mode
108
+ if options['health_check']:
109
+ self._perform_health_check(registry, options)
110
+ elif options['stats']:
111
+ self._show_provider_stats(registry, options)
112
+ elif options['provider']:
113
+ self._sync_single_provider(registry, options['provider'], options)
114
+ elif options['all']:
115
+ self._sync_all_providers(registry, options)
116
+ else:
117
+ # Default: show available providers and basic stats
118
+ self._show_available_providers(registry, options)
89
119
 
90
- # Update rates if requested
91
- if options['with_rates'] and not options['dry_run']:
92
- self.stdout.write("\nšŸ’± Updating USD exchange rates...")
93
- self._update_rates()
120
+ # Show summary
121
+ elapsed = time.time() - start_time
122
+ self.stdout.write(
123
+ self.style.SUCCESS(
124
+ f'āœ… Provider management completed in {elapsed:.1f}s'
125
+ )
126
+ )
94
127
 
95
- # Show summary
96
- elapsed = time.time() - start_time
97
- self.stdout.write('=' * 60)
98
- self.stdout.write(f"šŸ“Š Total items synced: {total_synced}")
99
- self.stdout.write(f"ā±ļø Completed in {elapsed:.2f} seconds")
100
- self.stdout.write('=' * 60)
128
+ except Exception as e:
129
+ self.stdout.write(
130
+ self.style.ERROR(f'āŒ Provider management failed: {e}')
131
+ )
132
+ logger.error(f"Provider management command failed: {e}")
133
+ raise CommandError(f"Command failed: {e}")
134
+
135
+ def _sync_all_providers(self, registry, options):
136
+ """Sync all active providers."""
137
+
138
+ self.stdout.write("šŸ”„ Syncing all active providers...")
139
+
140
+ available_providers = registry.get_available_providers()
141
+
142
+ if not available_providers:
143
+ self.stdout.write("āš ļø No active providers found")
144
+ return
145
+
146
+ total_synced = 0
147
+ total_errors = 0
148
+
149
+ for provider_name in available_providers:
150
+ try:
151
+ synced_count = self._sync_provider(registry, provider_name, options)
152
+ total_synced += synced_count
153
+
154
+ except Exception as e:
155
+ self.stdout.write(f"āŒ Failed to sync {provider_name}: {e}")
156
+ total_errors += 1
157
+ logger.error(f"Provider sync failed for {provider_name}: {e}")
101
158
 
102
- # Commands should not return values to stdout
103
- pass
159
+ self.stdout.write(
160
+ f"šŸ”„ All providers sync complete: {total_synced} currencies synced, {total_errors} errors"
161
+ )
104
162
 
105
- def _sync_provider(self, provider_name: str, options: dict) -> int:
163
+ # Update rates if requested
164
+ if options['with_rates'] and not options['dry_run']:
165
+ self._update_rates_after_sync(options)
166
+
167
+ def _sync_single_provider(self, registry, provider_name: str, options):
106
168
  """Sync a specific provider."""
107
- self.stdout.write(f"\nšŸ”„ Syncing {provider_name}...")
169
+
170
+ self.stdout.write(f"šŸ”„ Syncing provider: {provider_name}")
108
171
 
109
172
  try:
110
- provider = get_payment_provider(provider_name)
173
+ synced_count = self._sync_provider(registry, provider_name, options)
174
+ self.stdout.write(f"āœ… {provider_name} sync complete: {synced_count} currencies")
111
175
 
112
- if options['verbose']:
113
- config = provider.config
114
- self.stdout.write(f" šŸ“” Provider: {provider.__class__.__name__}")
115
- self.stdout.write(f" šŸ”§ Config: enabled={config.enabled} timeout_seconds={config.timeout_seconds} sandbox={getattr(config, 'sandbox', 'N/A')}")
176
+ # Update rates if requested
177
+ if options['with_rates'] and not options['dry_run']:
178
+ self._update_rates_after_sync(options)
179
+
180
+ except Exception as e:
181
+ self.stdout.write(f"āŒ Failed to sync {provider_name}: {e}")
182
+ logger.error(f"Provider sync failed for {provider_name}: {e}")
183
+ raise CommandError(f"Provider sync failed: {e}")
184
+
185
+ def _sync_provider(self, registry, provider_name: str, options) -> int:
186
+ """Sync currencies for a specific provider."""
187
+
188
+ try:
189
+ provider = registry.get_provider(provider_name)
190
+ if not provider:
191
+ raise CommandError(f"Provider '{provider_name}' not available")
192
+
193
+ self.stdout.write(f" šŸ“” Fetching currencies from {provider_name}...")
116
194
 
117
195
  if options['dry_run']:
118
- # Dry run: just get parsed currencies to show what would be synced
119
- try:
120
- parsed_response = provider.get_parsed_currencies()
121
- currency_count = len(parsed_response.currencies)
122
-
123
- # Calculate unique networks
124
- networks = set()
125
- for currency in parsed_response.currencies:
126
- if currency.network_code:
127
- networks.add(currency.network_code)
128
- network_count = len(networks)
129
-
130
- self.stdout.write(f" šŸ’° Would sync {currency_count} currencies")
131
- self.stdout.write(f" 🌐 Would sync {network_count} networks")
132
-
133
- return currency_count + network_count
134
-
135
- except Exception as e:
136
- self.stdout.write(f" āŒ Failed to fetch currencies: {e}")
137
- return 0
196
+ self.stdout.write(f" [DRY RUN] Would sync {provider_name} currencies")
197
+ return 0
198
+
199
+ # Use provider's sync method
200
+ sync_result = provider.sync_currencies_to_db()
201
+
202
+ if sync_result.errors:
203
+ for error in sync_result.errors:
204
+ self.stdout.write(f" āš ļø {error}")
205
+
206
+ synced_count = sync_result.currencies_created + sync_result.currencies_updated
138
207
 
139
- else:
140
- # Live sync
141
- with transaction.atomic():
142
- sync_result = provider.sync_currencies_to_db()
143
-
144
- if options['verbose']:
145
- self.stdout.write(f" āœ… Synced {sync_result.total_items_processed} items")
146
- if sync_result.errors:
147
- self.stdout.write(f" āš ļø Errors: {len(sync_result.errors)}")
148
- for error in sync_result.errors[:3]: # Show first 3 errors
149
- self.stdout.write(f" • {error}")
150
-
151
- self.stdout.write(
152
- self.style.SUCCESS(f"āœ… {provider_name}: {sync_result.total_items_processed} items synced")
153
- )
154
-
155
- return sync_result.total_items_processed
156
-
157
- except Exception as e:
158
- logger.exception(f"Error syncing provider {provider_name}")
159
208
  self.stdout.write(
160
- self.style.ERROR(f"āŒ Failed to sync {provider_name}: {e}")
209
+ f" āœ… {provider_name}: {sync_result.currencies_created} created, "
210
+ f"{sync_result.currencies_updated} updated, "
211
+ f"{sync_result.provider_currencies_created} provider mappings created"
161
212
  )
162
- return 0
163
213
 
164
- def _update_rates(self):
165
- """Update USD rates for currencies."""
166
- try:
167
- # Get currencies that need rate updates
168
- from datetime import timedelta
169
- from django.db.models import Q
170
-
171
- stale_threshold = timezone.now() - timedelta(hours=12)
172
- currencies_to_update = Currency.objects.filter(
173
- Q(usd_rate__isnull=True) |
174
- Q(rate_updated_at__isnull=True) |
175
- Q(rate_updated_at__lt=stale_threshold)
176
- )[:50] # Limit to avoid long execution
177
-
178
- updated_count = 0
179
- for currency in currencies_to_update:
180
- try:
181
- rate = Currency.objects.get_usd_rate(currency.code, force_refresh=True)
182
- if rate > 0:
183
- updated_count += 1
184
- self.stdout.write(f" āœ… {currency.code}: ${rate:.8f}")
185
- except Exception as e:
186
- self.stdout.write(f" āš ļø {currency.code}: {str(e)}")
187
-
188
- self.stdout.write(f"šŸ’± Updated {updated_count} exchange rates")
214
+ if options['verbose']:
215
+ self._show_provider_sync_details(sync_result)
189
216
 
190
- except Exception as e:
191
- self.stdout.write(f"āš ļø Rate update failed: {e}")
217
+ return synced_count
192
218
 
193
- def _show_stats(self):
194
- """Show provider statistics."""
195
- self.stdout.write("šŸ“Š Provider Statistics")
196
- self.stdout.write("-" * 40)
219
+ except Exception as e:
220
+ logger.error(f"Provider sync failed for {provider_name}: {e}")
221
+ raise
222
+
223
+ def _perform_health_check(self, registry, options):
224
+ """Perform health check on all providers."""
225
+
226
+ self.stdout.write("šŸ„ Performing provider health check...")
227
+
228
+ available_providers = registry.get_available_providers()
229
+
230
+ if not available_providers:
231
+ self.stdout.write("āš ļø No providers configured")
232
+ return
233
+
234
+ healthy_count = 0
235
+ unhealthy_count = 0
197
236
 
198
- # Available providers
199
- available_providers = get_available_providers()
200
- self.stdout.write(f"šŸ¢ Available providers: {len(available_providers)}")
201
237
  for provider_name in available_providers:
202
238
  try:
203
- provider = get_payment_provider(provider_name)
204
- enabled = provider.config.enabled
205
- status = "āœ… Enabled" if enabled else "āŒ Disabled"
206
- self.stdout.write(f" • {provider_name}: {status}")
239
+ provider = registry.get_provider(provider_name)
240
+
241
+ if not provider:
242
+ self.stdout.write(f" āŒ {provider_name}: Not available")
243
+ unhealthy_count += 1
244
+ continue
245
+
246
+ # Check if provider is enabled
247
+ if not provider.is_enabled():
248
+ self.stdout.write(f" āøļø {provider_name}: Disabled")
249
+ continue
250
+
251
+ # Perform basic health check (try to get currencies)
252
+ start_time = time.time()
253
+
254
+ try:
255
+ currencies = provider.get_parsed_currencies()
256
+ response_time = time.time() - start_time
257
+
258
+ if currencies and len(currencies.currencies) > 0:
259
+ self.stdout.write(
260
+ f" āœ… {provider_name}: Healthy "
261
+ f"({len(currencies.currencies)} currencies, {response_time:.2f}s)"
262
+ )
263
+ healthy_count += 1
264
+ else:
265
+ self.stdout.write(f" āš ļø {provider_name}: No currencies returned")
266
+ unhealthy_count += 1
267
+
268
+ except Exception as e:
269
+ response_time = time.time() - start_time
270
+ self.stdout.write(
271
+ f" āŒ {provider_name}: Error ({response_time:.2f}s) - {str(e)[:50]}"
272
+ )
273
+ unhealthy_count += 1
274
+
207
275
  except Exception as e:
208
- self.stdout.write(f" • {provider_name}: āŒ Error ({e})")
276
+ self.stdout.write(f" āŒ {provider_name}: Critical error - {e}")
277
+ unhealthy_count += 1
278
+
279
+ self.stdout.write(
280
+ f"šŸ„ Health check complete: {healthy_count} healthy, {unhealthy_count} unhealthy"
281
+ )
282
+
283
+ def _show_provider_stats(self, registry, options):
284
+ """Show detailed provider statistics."""
285
+
286
+ self.stdout.write("šŸ“Š Provider Statistics:")
209
287
 
210
- self.stdout.write()
288
+ # Registry stats
289
+ available_providers = registry.get_available_providers()
290
+ self.stdout.write(f"\nšŸ”§ Registry Status:")
291
+ self.stdout.write(f" Available providers: {len(available_providers)}")
292
+
293
+ for provider_name in available_providers:
294
+ provider = registry.get_provider(provider_name)
295
+ status = "āœ… Enabled" if provider and provider.is_enabled() else "āŒ Disabled"
296
+ self.stdout.write(f" - {provider_name}: {status}")
297
+
298
+ # Database stats
299
+ self.stdout.write(f"\nšŸ’¾ Database Statistics:")
211
300
 
212
- # Database statistics
213
- total_currencies = Currency.objects.count()
214
301
  total_provider_currencies = ProviderCurrency.objects.count()
302
+ enabled_provider_currencies = ProviderCurrency.objects.filter(is_enabled=True).count()
215
303
 
216
- self.stdout.write(f"šŸ’° Total currencies: {total_currencies}")
217
- self.stdout.write(f"šŸ”— Total provider currencies: {total_provider_currencies}")
304
+ self.stdout.write(f" Total provider currencies: {total_provider_currencies}")
305
+ self.stdout.write(f" Enabled: {enabled_provider_currencies}")
218
306
 
219
- # Provider breakdown
307
+ # Stats by provider
220
308
  from django.db.models import Count
309
+
221
310
  provider_stats = ProviderCurrency.objects.values('provider_name').annotate(
222
- count=Count('id')
223
- ).order_by('-count')
311
+ total=Count('id'),
312
+ enabled=Count('id', filter=models.Q(is_enabled=True))
313
+ ).order_by('-total')
224
314
 
225
- self.stdout.write("\nšŸ“Š Currencies by provider:")
226
- for stat in provider_stats:
227
- self.stdout.write(f" • {stat['provider_name']}: {stat['count']} currencies")
228
-
229
- # Rate statistics
230
- currencies_with_rates = Currency.objects.exclude(usd_rate__isnull=True).exclude(usd_rate=0)
231
- rate_coverage = (currencies_with_rates.count() / total_currencies * 100) if total_currencies > 0 else 0
315
+ if provider_stats:
316
+ self.stdout.write(f"\nšŸ“ˆ By Provider:")
317
+ for stat in provider_stats:
318
+ self.stdout.write(
319
+ f" - {stat['provider_name']}: {stat['total']} total, {stat['enabled']} enabled"
320
+ )
321
+
322
+ # Recent activity
323
+ recent_threshold = timezone.now() - timedelta(hours=24)
324
+ recent_updates = ProviderCurrency.objects.filter(
325
+ updated_at__gte=recent_threshold
326
+ ).count()
327
+
328
+ self.stdout.write(f"\nšŸ• Recent Activity (24h):")
329
+ self.stdout.write(f" Updated currencies: {recent_updates}")
330
+
331
+ # Rate coverage
332
+ currencies_with_rates = ProviderCurrency.objects.filter(
333
+ usd_rate__isnull=False
334
+ ).count()
335
+
336
+ rate_coverage = (currencies_with_rates / total_provider_currencies * 100) if total_provider_currencies > 0 else 0
337
+
338
+ self.stdout.write(f"\nšŸ’± Rate Coverage:")
339
+ self.stdout.write(f" Currencies with USD rates: {currencies_with_rates} ({rate_coverage:.1f}%)")
340
+
341
+ def _show_available_providers(self, registry, options):
342
+ """Show available providers and basic info."""
343
+
344
+ self.stdout.write("šŸ“‹ Available Providers:")
345
+
346
+ available_providers = registry.get_available_providers()
347
+
348
+ if not available_providers:
349
+ self.stdout.write(" No providers configured")
350
+ return
351
+
352
+ for provider_name in available_providers:
353
+ try:
354
+ provider = registry.get_provider(provider_name)
355
+
356
+ if provider:
357
+ status = "āœ… Enabled" if provider.is_enabled() else "āŒ Disabled"
358
+
359
+ # Get currency count from database
360
+ currency_count = ProviderCurrency.objects.filter(
361
+ provider_name=provider_name
362
+ ).count()
363
+
364
+ self.stdout.write(
365
+ f" - {provider_name}: {status} ({currency_count} currencies)"
366
+ )
367
+ else:
368
+ self.stdout.write(f" - {provider_name}: āŒ Not available")
369
+
370
+ except Exception as e:
371
+ self.stdout.write(f" - {provider_name}: āŒ Error - {e}")
372
+
373
+ self.stdout.write(f"\nUse --all to sync all providers or --provider <name> for specific provider")
374
+
375
+ def _show_provider_sync_details(self, sync_result):
376
+ """Show detailed sync results."""
232
377
 
233
- self.stdout.write(f"\nšŸ’µ USD rate coverage: {rate_coverage:.1f}% ({currencies_with_rates.count()}/{total_currencies})")
378
+ self.stdout.write(" šŸ“‹ Sync Details:")
379
+ self.stdout.write(f" Currencies created: {sync_result.currencies_created}")
380
+ self.stdout.write(f" Currencies updated: {sync_result.currencies_updated}")
381
+ self.stdout.write(f" Networks created: {sync_result.networks_created}")
382
+ self.stdout.write(f" Provider currencies created: {sync_result.provider_currencies_created}")
383
+ self.stdout.write(f" Provider currencies updated: {sync_result.provider_currencies_updated}")
234
384
 
235
- # Stats command should not return values
385
+ if sync_result.errors:
386
+ self.stdout.write(f" Errors: {len(sync_result.errors)}")
387
+
388
+ def _update_rates_after_sync(self, options):
389
+ """Update USD rates after provider sync."""
390
+
391
+ self.stdout.write("šŸ’± Updating USD rates after sync...")
392
+
393
+ try:
394
+ from django.core.management import call_command
395
+
396
+ # Update rates for currencies that were just synced
397
+ if options['currencies']:
398
+ currency_codes = options['currencies'].split(',')
399
+ for code in currency_codes:
400
+ call_command('manage_currencies', '--currency', code.strip(), '--rates-only')
401
+ else:
402
+ call_command('manage_currencies', '--rates-only')
403
+
404
+ self.stdout.write("šŸ’± Rate update completed")
405
+
406
+ except Exception as e:
407
+ self.stdout.write(f"āš ļø Rate update failed: {e}")
408
+ logger.warning(f"Rate update after sync failed: {e}")