django-cfg 1.3.1__py3-none-any.whl → 1.3.5__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 (115) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin_interface/old/payments/base.html +175 -0
  3. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +125 -0
  4. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +113 -0
  5. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +35 -0
  6. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +309 -0
  7. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +303 -0
  8. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +382 -0
  9. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +518 -0
  10. django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/components.css +248 -9
  11. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +163 -0
  12. django_cfg/apps/payments/admin_interface/serializers/__init__.py +39 -0
  13. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +149 -0
  14. django_cfg/apps/payments/admin_interface/serializers/webhook_serializers.py +114 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/base.html +55 -90
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/dialog.html +81 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_help_dialog.html +112 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_status.html +175 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +21 -17
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +123 -250
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +170 -269
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +152 -355
  23. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +202 -551
  24. django_cfg/apps/payments/admin_interface/views/__init__.py +25 -14
  25. django_cfg/apps/payments/admin_interface/views/api/__init__.py +20 -0
  26. django_cfg/apps/payments/admin_interface/views/api/payments.py +191 -0
  27. django_cfg/apps/payments/admin_interface/views/api/stats.py +206 -0
  28. django_cfg/apps/payments/admin_interface/views/api/users.py +60 -0
  29. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +257 -0
  30. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +70 -0
  31. django_cfg/apps/payments/admin_interface/views/base.py +114 -0
  32. django_cfg/apps/payments/admin_interface/views/dashboard.py +60 -0
  33. django_cfg/apps/payments/admin_interface/views/forms.py +94 -0
  34. django_cfg/apps/payments/config/helpers.py +2 -2
  35. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +429 -0
  36. django_cfg/apps/payments/management/commands/currency_stats.py +443 -0
  37. django_cfg/apps/payments/management/commands/manage_currencies.py +9 -20
  38. django_cfg/apps/payments/management/commands/manage_providers.py +5 -5
  39. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  40. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  41. django_cfg/apps/payments/middleware/api_access.py +35 -34
  42. django_cfg/apps/payments/migrations/0001_initial.py +1 -1
  43. django_cfg/apps/payments/models/balance.py +5 -2
  44. django_cfg/apps/payments/models/managers/api_key_managers.py +6 -2
  45. django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
  46. django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
  47. django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
  48. django_cfg/apps/payments/models/subscriptions.py +0 -24
  49. django_cfg/apps/payments/services/cache/__init__.py +1 -1
  50. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  51. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  52. django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
  53. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  54. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  55. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  56. django_cfg/apps/payments/services/core/balance_service.py +13 -2
  57. django_cfg/apps/payments/services/core/payment_service.py +49 -22
  58. django_cfg/apps/payments/services/integrations/ngrok_service.py +3 -3
  59. django_cfg/apps/payments/services/providers/registry.py +20 -0
  60. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  61. django_cfg/apps/payments/signals/balance_signals.py +8 -5
  62. django_cfg/apps/payments/static/payments/js/api-client.js +385 -0
  63. django_cfg/apps/payments/static/payments/js/ngrok-status.js +58 -0
  64. django_cfg/apps/payments/static/payments/js/payment-dashboard.js +50 -0
  65. django_cfg/apps/payments/static/payments/js/payment-form.js +175 -0
  66. django_cfg/apps/payments/static/payments/js/payment-list.js +95 -0
  67. django_cfg/apps/payments/static/payments/js/webhook-dashboard.js +154 -0
  68. django_cfg/apps/payments/urls.py +4 -0
  69. django_cfg/apps/payments/urls_admin.py +37 -18
  70. django_cfg/apps/payments/views/api/api_keys.py +14 -0
  71. django_cfg/apps/payments/views/api/base.py +1 -0
  72. django_cfg/apps/payments/views/api/currencies.py +2 -2
  73. django_cfg/apps/payments/views/api/payments.py +11 -5
  74. django_cfg/apps/payments/views/api/subscriptions.py +36 -31
  75. django_cfg/apps/payments/views/overview/__init__.py +40 -0
  76. django_cfg/apps/payments/views/overview/serializers.py +205 -0
  77. django_cfg/apps/payments/views/overview/services.py +439 -0
  78. django_cfg/apps/payments/views/overview/urls.py +27 -0
  79. django_cfg/apps/payments/views/overview/views.py +231 -0
  80. django_cfg/apps/payments/views/serializers/api_keys.py +20 -6
  81. django_cfg/apps/payments/views/serializers/balances.py +5 -8
  82. django_cfg/apps/payments/views/serializers/currencies.py +2 -6
  83. django_cfg/apps/payments/views/serializers/payments.py +37 -32
  84. django_cfg/apps/payments/views/serializers/subscriptions.py +4 -26
  85. django_cfg/apps/urls.py +2 -1
  86. django_cfg/core/config.py +25 -15
  87. django_cfg/core/generation.py +12 -12
  88. django_cfg/core/integration/display/startup.py +1 -1
  89. django_cfg/core/validation.py +4 -4
  90. django_cfg/management/commands/show_config.py +2 -2
  91. django_cfg/management/commands/tree.py +1 -3
  92. django_cfg/middleware/__init__.py +2 -0
  93. django_cfg/middleware/static_nocache.py +55 -0
  94. django_cfg/models/payments.py +13 -15
  95. django_cfg/models/security.py +15 -0
  96. django_cfg/modules/django_ngrok.py +6 -0
  97. django_cfg/modules/django_unfold/dashboard.py +1 -3
  98. django_cfg/utils/smart_defaults.py +51 -5
  99. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
  100. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/RECORD +111 -69
  101. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +0 -38
  102. django_cfg/apps/payments/admin_interface/views/payment_views.py +0 -259
  103. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +0 -37
  104. django_cfg/apps/payments/services/cache/cache_service.py +0 -235
  105. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/loading_spinner.html +0 -0
  106. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/notification.html +0 -0
  107. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/provider_card.html +0 -0
  108. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/currency_converter.html +0 -0
  109. /django_cfg/apps/payments/admin_interface/{templates → old}/payments/payment_status.html +0 -0
  110. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/dashboard.css +0 -0
  111. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/components.js +0 -0
  112. /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/utils.js +0 -0
  113. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
  114. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
  115. {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,357 @@
1
+ """
2
+ Process Pending Payments Management Command for Universal Payment System v2.0.
3
+
4
+ Automatically process pending payments, check statuses, and handle timeouts.
5
+ """
6
+
7
+ from datetime import timedelta
8
+ from typing import List, Dict, Any, Optional
9
+ from decimal import Decimal
10
+
11
+ from django.core.management.base import BaseCommand, CommandError
12
+ from django.utils import timezone
13
+ from django.db import transaction
14
+ from django.db.models import Q
15
+
16
+ from django_cfg.modules.django_logger import get_logger
17
+ from django_cfg.apps.payments.models import UniversalPayment
18
+ from django_cfg.apps.payments.services.providers.registry import get_provider_registry
19
+ from django_cfg.apps.payments.services.core.payment_service import PaymentService
20
+ from django_cfg.apps.payments.services.core.webhook_service import WebhookService
21
+
22
+ logger = get_logger("process_pending_payments")
23
+
24
+
25
+ class Command(BaseCommand):
26
+ """
27
+ Process pending payments and update their statuses.
28
+
29
+ Features:
30
+ - Check payment statuses with providers
31
+ - Handle expired payments
32
+ - Process confirmations
33
+ - Update balances for completed payments
34
+ - Comprehensive logging and error handling
35
+ """
36
+
37
+ help = 'Process pending payments and update their statuses'
38
+
39
+ def add_arguments(self, parser):
40
+ """Add command line arguments."""
41
+ parser.add_argument(
42
+ '--provider',
43
+ type=str,
44
+ help='Process payments for specific provider only'
45
+ )
46
+
47
+ parser.add_argument(
48
+ '--payment-id',
49
+ type=str,
50
+ help='Process specific payment by ID'
51
+ )
52
+
53
+ parser.add_argument(
54
+ '--max-age-hours',
55
+ type=int,
56
+ default=72,
57
+ help='Maximum age in hours for pending payments (default: 72)'
58
+ )
59
+
60
+ parser.add_argument(
61
+ '--batch-size',
62
+ type=int,
63
+ default=50,
64
+ help='Number of payments to process in each batch (default: 50)'
65
+ )
66
+
67
+ parser.add_argument(
68
+ '--dry-run',
69
+ action='store_true',
70
+ help='Show what would be processed without making changes'
71
+ )
72
+
73
+ parser.add_argument(
74
+ '--force-expired',
75
+ action='store_true',
76
+ help='Force expire payments older than max-age-hours'
77
+ )
78
+
79
+ parser.add_argument(
80
+ '--verbose',
81
+ action='store_true',
82
+ help='Show detailed processing information'
83
+ )
84
+
85
+ def handle(self, *args, **options):
86
+ """Execute the command."""
87
+ try:
88
+ self.options = options
89
+ self.dry_run = options['dry_run']
90
+ self.verbose = options['verbose']
91
+
92
+ self.show_header()
93
+
94
+ if options['payment_id']:
95
+ self.process_single_payment(options['payment_id'])
96
+ else:
97
+ self.process_pending_payments()
98
+
99
+ self.show_summary()
100
+
101
+ except Exception as e:
102
+ logger.error(f"Process pending payments command failed: {e}")
103
+ raise CommandError(f"Failed to process pending payments: {e}")
104
+
105
+ def show_header(self):
106
+ """Display command header."""
107
+ mode = "DRY RUN" if self.dry_run else "LIVE MODE"
108
+ self.stdout.write(
109
+ self.style.SUCCESS("=" * 60)
110
+ )
111
+ self.stdout.write(
112
+ self.style.SUCCESS(f"⚡ PROCESS PENDING PAYMENTS - {mode}")
113
+ )
114
+ self.stdout.write(
115
+ self.style.SUCCESS("=" * 60)
116
+ )
117
+ self.stdout.write(f"Started: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
118
+ self.stdout.write("")
119
+
120
+ # Initialize counters
121
+ self.stats = {
122
+ 'processed': 0,
123
+ 'completed': 0,
124
+ 'failed': 0,
125
+ 'expired': 0,
126
+ 'errors': 0,
127
+ 'skipped': 0
128
+ }
129
+
130
+ def process_single_payment(self, payment_id: str):
131
+ """Process a single payment by ID."""
132
+ try:
133
+ payment = UniversalPayment.objects.get(id=payment_id)
134
+ self.stdout.write(f"Processing single payment: {payment.id}")
135
+
136
+ result = self.process_payment(payment)
137
+ if result:
138
+ self.stdout.write(
139
+ self.style.SUCCESS(f"✅ Payment processed successfully")
140
+ )
141
+ else:
142
+ self.stdout.write(
143
+ self.style.ERROR(f"❌ Failed to process payment")
144
+ )
145
+
146
+ except UniversalPayment.DoesNotExist:
147
+ raise CommandError(f"Payment with ID {payment_id} not found")
148
+
149
+ def process_pending_payments(self):
150
+ """Process all pending payments."""
151
+ # Build query filters
152
+ filters = Q(status__in=['pending', 'confirming', 'confirmed'])
153
+
154
+ # Provider filter
155
+ if self.options['provider']:
156
+ filters &= Q(provider=self.options['provider'])
157
+
158
+ # Age filter
159
+ max_age = timezone.now() - timedelta(hours=self.options['max_age_hours'])
160
+ if not self.options['force_expired']:
161
+ filters &= Q(created_at__gte=max_age)
162
+
163
+ # Get payments to process
164
+ payments = UniversalPayment.objects.filter(filters).select_related(
165
+ 'user', 'currency', 'network'
166
+ ).order_by('created_at')
167
+
168
+ total_payments = payments.count()
169
+ self.stdout.write(f"Found {total_payments} payments to process")
170
+
171
+ if total_payments == 0:
172
+ self.stdout.write(self.style.WARNING("No payments to process"))
173
+ return
174
+
175
+ # Process in batches
176
+ batch_size = self.options['batch_size']
177
+ processed = 0
178
+
179
+ for i in range(0, total_payments, batch_size):
180
+ batch = payments[i:i + batch_size]
181
+ self.stdout.write(f"\nProcessing batch {i//batch_size + 1} ({len(batch)} payments)...")
182
+
183
+ for payment in batch:
184
+ try:
185
+ self.process_payment(payment)
186
+ processed += 1
187
+
188
+ if self.verbose:
189
+ self.stdout.write(f" Processed: {payment.id} ({payment.status})")
190
+
191
+ except Exception as e:
192
+ self.stats['errors'] += 1
193
+ logger.error(f"Error processing payment {payment.id}: {e}")
194
+ if self.verbose:
195
+ self.stdout.write(
196
+ self.style.ERROR(f" Error: {payment.id} - {e}")
197
+ )
198
+
199
+ # Show progress
200
+ progress = (processed / total_payments) * 100
201
+ self.stdout.write(f"Progress: {processed}/{total_payments} ({progress:.1f}%)")
202
+
203
+ def process_payment(self, payment: UniversalPayment) -> bool:
204
+ """
205
+ Process a single payment.
206
+
207
+ Returns:
208
+ bool: True if payment was processed successfully
209
+ """
210
+ try:
211
+ # Check if payment is too old and should be expired
212
+ max_age = timezone.now() - timedelta(hours=self.options['max_age_hours'])
213
+ if payment.created_at < max_age and self.options['force_expired']:
214
+ return self.expire_payment(payment)
215
+
216
+ # Check if payment has explicit expiration
217
+ if payment.expires_at and payment.expires_at < timezone.now():
218
+ return self.expire_payment(payment)
219
+
220
+ # Get provider and check status
221
+ provider_registry = get_provider_registry()
222
+ provider = provider_registry.get_provider(payment.provider)
223
+
224
+ if not provider:
225
+ logger.warning(f"Provider {payment.provider} not available for payment {payment.id}")
226
+ self.stats['skipped'] += 1
227
+ return False
228
+
229
+ # Check payment status with provider
230
+ if self.dry_run:
231
+ self.stdout.write(f" [DRY RUN] Would check status for payment {payment.id}")
232
+ self.stats['processed'] += 1
233
+ return True
234
+
235
+ # Get current status from provider
236
+ status_result = provider.get_payment_status(payment.provider_payment_id)
237
+
238
+ if not status_result.success:
239
+ logger.warning(f"Failed to get status for payment {payment.id}: {status_result.error}")
240
+ self.stats['errors'] += 1
241
+ return False
242
+
243
+ # Update payment based on provider status
244
+ old_status = payment.status
245
+ new_status = status_result.status
246
+
247
+ if old_status != new_status:
248
+ with transaction.atomic():
249
+ payment.status = new_status
250
+
251
+ # Update additional fields if provided
252
+ if hasattr(status_result, 'transaction_hash') and status_result.transaction_hash:
253
+ payment.transaction_hash = status_result.transaction_hash
254
+
255
+ if hasattr(status_result, 'confirmations') and status_result.confirmations is not None:
256
+ payment.confirmations_count = status_result.confirmations
257
+
258
+ if new_status == 'completed':
259
+ payment.completed_at = timezone.now()
260
+ self.stats['completed'] += 1
261
+
262
+ # Process balance update using service
263
+ payment_service = PaymentService()
264
+ payment_service.process_completed_payment(payment)
265
+
266
+ elif new_status in ['failed', 'expired', 'cancelled']:
267
+ self.stats['failed'] += 1
268
+
269
+ payment.save()
270
+
271
+ logger.info(f"Payment {payment.id} status updated: {old_status} -> {new_status}")
272
+
273
+ if self.verbose:
274
+ self.stdout.write(f" Updated: {payment.id} ({old_status} -> {new_status})")
275
+
276
+ self.stats['processed'] += 1
277
+ return True
278
+
279
+ except Exception as e:
280
+ logger.error(f"Error processing payment {payment.id}: {e}")
281
+ self.stats['errors'] += 1
282
+ return False
283
+
284
+ def expire_payment(self, payment: UniversalPayment) -> bool:
285
+ """
286
+ Expire a payment that is too old.
287
+
288
+ Args:
289
+ payment: Payment to expire
290
+
291
+ Returns:
292
+ bool: True if payment was expired successfully
293
+ """
294
+ try:
295
+ if self.dry_run:
296
+ self.stdout.write(f" [DRY RUN] Would expire payment {payment.id}")
297
+ return True
298
+
299
+ with transaction.atomic():
300
+ old_status = payment.status
301
+ payment.status = 'expired'
302
+ payment.save()
303
+
304
+ self.stats['expired'] += 1
305
+ logger.info(f"Payment {payment.id} expired (was {old_status})")
306
+
307
+ if self.verbose:
308
+ self.stdout.write(f" Expired: {payment.id} (was {old_status})")
309
+
310
+ return True
311
+
312
+ except Exception as e:
313
+ logger.error(f"Error expiring payment {payment.id}: {e}")
314
+ self.stats['errors'] += 1
315
+ return False
316
+
317
+ def show_summary(self):
318
+ """Display processing summary."""
319
+ self.stdout.write("")
320
+ self.stdout.write(self.style.SUCCESS("📊 PROCESSING SUMMARY"))
321
+ self.stdout.write("-" * 40)
322
+
323
+ summary_items = [
324
+ ("Processed", self.stats['processed'], 'SUCCESS'),
325
+ ("Completed", self.stats['completed'], 'SUCCESS'),
326
+ ("Failed", self.stats['failed'], 'WARNING'),
327
+ ("Expired", self.stats['expired'], 'WARNING'),
328
+ ("Errors", self.stats['errors'], 'ERROR'),
329
+ ("Skipped", self.stats['skipped'], 'WARNING'),
330
+ ]
331
+
332
+ for label, count, style in summary_items:
333
+ style_func = getattr(self.style, style)
334
+ self.stdout.write(f"{label:<12}: {style_func(count)}")
335
+
336
+ # Show completion time
337
+ self.stdout.write("")
338
+ self.stdout.write(f"Completed: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
339
+
340
+ # Show recommendations
341
+ if self.stats['errors'] > 0:
342
+ self.stdout.write("")
343
+ self.stdout.write(
344
+ self.style.WARNING("⚠️ Some payments had errors. Check logs for details.")
345
+ )
346
+
347
+ if self.stats['failed'] > 0:
348
+ self.stdout.write("")
349
+ self.stdout.write(
350
+ self.style.WARNING("⚠️ Some payments failed. Consider investigating failed payments.")
351
+ )
352
+
353
+ if self.dry_run:
354
+ self.stdout.write("")
355
+ self.stdout.write(
356
+ self.style.SUCCESS("✅ Dry run completed. Run without --dry-run to apply changes.")
357
+ )