django-cfg 1.2.31__py3-none-any.whl → 1.3.1__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 (256) 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/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.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}")
@@ -1,5 +1,7 @@
1
1
  """
2
- Middleware for universal payments.
2
+ Middleware for the Universal Payment System v2.0.
3
+
4
+ Enhanced middleware with service layer integration and smart caching.
3
5
  """
4
6
 
5
7
  from .api_access import APIAccessMiddleware