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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/payments/admin_interface/old/payments/base.html +175 -0
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +125 -0
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +113 -0
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +35 -0
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +309 -0
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +518 -0
- django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/components.css +248 -9
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +163 -0
- django_cfg/apps/payments/admin_interface/serializers/__init__.py +39 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +149 -0
- django_cfg/apps/payments/admin_interface/serializers/webhook_serializers.py +114 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +55 -90
- django_cfg/apps/payments/admin_interface/templates/payments/components/dialog.html +81 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_help_dialog.html +112 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/ngrok_status.html +175 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +21 -17
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +123 -250
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +170 -269
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +152 -355
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +202 -551
- django_cfg/apps/payments/admin_interface/views/__init__.py +25 -14
- django_cfg/apps/payments/admin_interface/views/api/__init__.py +20 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +191 -0
- django_cfg/apps/payments/admin_interface/views/api/stats.py +206 -0
- django_cfg/apps/payments/admin_interface/views/api/users.py +60 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +257 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +70 -0
- django_cfg/apps/payments/admin_interface/views/base.py +114 -0
- django_cfg/apps/payments/admin_interface/views/dashboard.py +60 -0
- django_cfg/apps/payments/admin_interface/views/forms.py +94 -0
- django_cfg/apps/payments/config/helpers.py +2 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +429 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +443 -0
- django_cfg/apps/payments/management/commands/manage_currencies.py +9 -20
- django_cfg/apps/payments/management/commands/manage_providers.py +5 -5
- django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
- django_cfg/apps/payments/management/commands/test_providers.py +434 -0
- django_cfg/apps/payments/middleware/api_access.py +35 -34
- django_cfg/apps/payments/migrations/0001_initial.py +1 -1
- django_cfg/apps/payments/models/balance.py +5 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +6 -2
- django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
- django_cfg/apps/payments/models/managers/payment_managers.py +5 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
- django_cfg/apps/payments/models/subscriptions.py +0 -24
- django_cfg/apps/payments/services/cache/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
- django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
- django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
- django_cfg/apps/payments/services/cache_service/keys.py +49 -0
- django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
- django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
- django_cfg/apps/payments/services/core/balance_service.py +13 -2
- django_cfg/apps/payments/services/core/payment_service.py +49 -22
- django_cfg/apps/payments/services/integrations/ngrok_service.py +3 -3
- django_cfg/apps/payments/services/providers/registry.py +20 -0
- django_cfg/apps/payments/signals/api_key_signals.py +2 -2
- django_cfg/apps/payments/signals/balance_signals.py +8 -5
- django_cfg/apps/payments/static/payments/js/api-client.js +385 -0
- django_cfg/apps/payments/static/payments/js/ngrok-status.js +58 -0
- django_cfg/apps/payments/static/payments/js/payment-dashboard.js +50 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +175 -0
- django_cfg/apps/payments/static/payments/js/payment-list.js +95 -0
- django_cfg/apps/payments/static/payments/js/webhook-dashboard.js +154 -0
- django_cfg/apps/payments/urls.py +4 -0
- django_cfg/apps/payments/urls_admin.py +37 -18
- django_cfg/apps/payments/views/api/api_keys.py +14 -0
- django_cfg/apps/payments/views/api/base.py +1 -0
- django_cfg/apps/payments/views/api/currencies.py +2 -2
- django_cfg/apps/payments/views/api/payments.py +11 -5
- django_cfg/apps/payments/views/api/subscriptions.py +36 -31
- django_cfg/apps/payments/views/overview/__init__.py +40 -0
- django_cfg/apps/payments/views/overview/serializers.py +205 -0
- django_cfg/apps/payments/views/overview/services.py +439 -0
- django_cfg/apps/payments/views/overview/urls.py +27 -0
- django_cfg/apps/payments/views/overview/views.py +231 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +20 -6
- django_cfg/apps/payments/views/serializers/balances.py +5 -8
- django_cfg/apps/payments/views/serializers/currencies.py +2 -6
- django_cfg/apps/payments/views/serializers/payments.py +37 -32
- django_cfg/apps/payments/views/serializers/subscriptions.py +4 -26
- django_cfg/apps/urls.py +2 -1
- django_cfg/core/config.py +25 -15
- django_cfg/core/generation.py +12 -12
- django_cfg/core/integration/display/startup.py +1 -1
- django_cfg/core/validation.py +4 -4
- django_cfg/management/commands/show_config.py +2 -2
- django_cfg/management/commands/tree.py +1 -3
- django_cfg/middleware/__init__.py +2 -0
- django_cfg/middleware/static_nocache.py +55 -0
- django_cfg/models/payments.py +13 -15
- django_cfg/models/security.py +15 -0
- django_cfg/modules/django_ngrok.py +6 -0
- django_cfg/modules/django_unfold/dashboard.py +1 -3
- django_cfg/utils/smart_defaults.py +51 -5
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/METADATA +1 -1
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/RECORD +111 -69
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +0 -38
- django_cfg/apps/payments/admin_interface/views/payment_views.py +0 -259
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +0 -37
- django_cfg/apps/payments/services/cache/cache_service.py +0 -235
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/loading_spinner.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/notification.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/components/provider_card.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/currency_converter.html +0 -0
- /django_cfg/apps/payments/admin_interface/{templates → old}/payments/payment_status.html +0 -0
- /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/css/dashboard.css +0 -0
- /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/components.js +0 -0
- /django_cfg/apps/payments/{static → admin_interface/old/static}/payments/js/utils.js +0 -0
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.1.dist-info → django_cfg-1.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,434 @@
|
|
1
|
+
"""
|
2
|
+
Test Providers Management Command for Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Test payment provider connections, configurations, and functionality.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import List, Dict, Any, Optional
|
8
|
+
from decimal import Decimal
|
9
|
+
import time
|
10
|
+
|
11
|
+
from django.core.management.base import BaseCommand, CommandError
|
12
|
+
from django.utils import timezone
|
13
|
+
|
14
|
+
from django_cfg.modules.django_logger import get_logger
|
15
|
+
from django_cfg.apps.payments.services.providers.registry import get_provider_registry
|
16
|
+
from django_cfg.apps.payments.models import Currency, Network
|
17
|
+
from django_cfg.apps.payments.services.types.requests import PaymentCreateRequest
|
18
|
+
|
19
|
+
logger = get_logger("test_providers")
|
20
|
+
|
21
|
+
|
22
|
+
class Command(BaseCommand):
|
23
|
+
"""
|
24
|
+
Test payment provider connections and functionality.
|
25
|
+
|
26
|
+
Features:
|
27
|
+
- Test provider connectivity
|
28
|
+
- Validate configurations
|
29
|
+
- Test currency support
|
30
|
+
- Check API responses
|
31
|
+
- Performance testing
|
32
|
+
"""
|
33
|
+
|
34
|
+
help = 'Test payment provider connections and functionality'
|
35
|
+
|
36
|
+
def add_arguments(self, parser):
|
37
|
+
"""Add command line arguments."""
|
38
|
+
parser.add_argument(
|
39
|
+
'--provider',
|
40
|
+
type=str,
|
41
|
+
help='Test specific provider only'
|
42
|
+
)
|
43
|
+
|
44
|
+
parser.add_argument(
|
45
|
+
'--test-type',
|
46
|
+
choices=['connectivity', 'currencies', 'payment', 'all'],
|
47
|
+
default='all',
|
48
|
+
help='Type of test to run (default: all)'
|
49
|
+
)
|
50
|
+
|
51
|
+
parser.add_argument(
|
52
|
+
'--timeout',
|
53
|
+
type=int,
|
54
|
+
default=30,
|
55
|
+
help='Request timeout in seconds (default: 30)'
|
56
|
+
)
|
57
|
+
|
58
|
+
parser.add_argument(
|
59
|
+
'--verbose',
|
60
|
+
action='store_true',
|
61
|
+
help='Show detailed test information'
|
62
|
+
)
|
63
|
+
|
64
|
+
parser.add_argument(
|
65
|
+
'--create-test-payment',
|
66
|
+
action='store_true',
|
67
|
+
help='Create actual test payment (use with caution!)'
|
68
|
+
)
|
69
|
+
|
70
|
+
parser.add_argument(
|
71
|
+
'--test-amount',
|
72
|
+
type=float,
|
73
|
+
default=1.0,
|
74
|
+
help='Test payment amount in USD (default: 1.0)'
|
75
|
+
)
|
76
|
+
|
77
|
+
def handle(self, *args, **options):
|
78
|
+
"""Execute the command."""
|
79
|
+
try:
|
80
|
+
self.options = options
|
81
|
+
self.verbose = options['verbose']
|
82
|
+
|
83
|
+
self.show_header()
|
84
|
+
|
85
|
+
# Get provider registry
|
86
|
+
self.provider_registry = get_provider_registry()
|
87
|
+
|
88
|
+
# Initialize test results
|
89
|
+
self.test_results = {}
|
90
|
+
|
91
|
+
# Get providers to test
|
92
|
+
if options['provider']:
|
93
|
+
providers = [options['provider']]
|
94
|
+
else:
|
95
|
+
providers = self.provider_registry.get_available_providers()
|
96
|
+
|
97
|
+
if not providers:
|
98
|
+
self.stdout.write(self.style.WARNING("No providers available to test"))
|
99
|
+
return
|
100
|
+
|
101
|
+
# Run tests
|
102
|
+
for provider_name in providers:
|
103
|
+
self.test_provider(provider_name)
|
104
|
+
|
105
|
+
self.show_summary()
|
106
|
+
|
107
|
+
except Exception as e:
|
108
|
+
logger.error(f"Test providers command failed: {e}")
|
109
|
+
raise CommandError(f"Failed to test providers: {e}")
|
110
|
+
|
111
|
+
def show_header(self):
|
112
|
+
"""Display command header."""
|
113
|
+
self.stdout.write(
|
114
|
+
self.style.SUCCESS("=" * 60)
|
115
|
+
)
|
116
|
+
self.stdout.write(
|
117
|
+
self.style.SUCCESS("🧪 PAYMENT PROVIDER TESTING")
|
118
|
+
)
|
119
|
+
self.stdout.write(
|
120
|
+
self.style.SUCCESS("=" * 60)
|
121
|
+
)
|
122
|
+
self.stdout.write(f"Started: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
123
|
+
self.stdout.write("")
|
124
|
+
|
125
|
+
def test_provider(self, provider_name: str):
|
126
|
+
"""Test a specific provider."""
|
127
|
+
self.stdout.write(self.style.SUCCESS(f"🔍 TESTING PROVIDER: {provider_name.upper()}"))
|
128
|
+
self.stdout.write("-" * 50)
|
129
|
+
|
130
|
+
# Initialize provider results
|
131
|
+
self.test_results[provider_name] = {
|
132
|
+
'connectivity': False,
|
133
|
+
'currencies': False,
|
134
|
+
'payment': False,
|
135
|
+
'errors': [],
|
136
|
+
'warnings': [],
|
137
|
+
'performance': {}
|
138
|
+
}
|
139
|
+
|
140
|
+
try:
|
141
|
+
# Get provider instance
|
142
|
+
provider = self.provider_registry.get_provider(provider_name)
|
143
|
+
if not provider:
|
144
|
+
self.test_results[provider_name]['errors'].append("Provider not available")
|
145
|
+
self.stdout.write(self.style.ERROR(f"❌ Provider {provider_name} not available"))
|
146
|
+
return
|
147
|
+
|
148
|
+
# Run selected tests
|
149
|
+
test_type = self.options['test_type']
|
150
|
+
|
151
|
+
if test_type in ['connectivity', 'all']:
|
152
|
+
self.test_connectivity(provider_name, provider)
|
153
|
+
|
154
|
+
if test_type in ['currencies', 'all']:
|
155
|
+
self.test_currencies(provider_name, provider)
|
156
|
+
|
157
|
+
if test_type in ['payment', 'all'] and self.options['create_test_payment']:
|
158
|
+
self.test_payment_creation(provider_name, provider)
|
159
|
+
|
160
|
+
except Exception as e:
|
161
|
+
error_msg = f"Provider test failed: {e}"
|
162
|
+
self.test_results[provider_name]['errors'].append(error_msg)
|
163
|
+
logger.error(f"Error testing provider {provider_name}: {e}")
|
164
|
+
self.stdout.write(self.style.ERROR(f"❌ {error_msg}"))
|
165
|
+
|
166
|
+
self.stdout.write("")
|
167
|
+
|
168
|
+
def test_connectivity(self, provider_name: str, provider):
|
169
|
+
"""Test provider connectivity."""
|
170
|
+
self.stdout.write(" 🌐 Testing connectivity...")
|
171
|
+
|
172
|
+
try:
|
173
|
+
start_time = time.time()
|
174
|
+
|
175
|
+
# Test basic connectivity (health check or similar)
|
176
|
+
if hasattr(provider, 'health_check'):
|
177
|
+
result = provider.health_check()
|
178
|
+
success = result.success if hasattr(result, 'success') else True
|
179
|
+
elif hasattr(provider, 'get_supported_currencies'):
|
180
|
+
# Fallback: try to get currencies as connectivity test
|
181
|
+
result = provider.get_supported_currencies()
|
182
|
+
success = len(result) > 0 if isinstance(result, list) else bool(result)
|
183
|
+
else:
|
184
|
+
# Last resort: assume connectivity is OK if provider exists
|
185
|
+
success = True
|
186
|
+
|
187
|
+
end_time = time.time()
|
188
|
+
response_time = (end_time - start_time) * 1000 # Convert to milliseconds
|
189
|
+
|
190
|
+
self.test_results[provider_name]['performance']['connectivity_ms'] = response_time
|
191
|
+
|
192
|
+
if success:
|
193
|
+
self.test_results[provider_name]['connectivity'] = True
|
194
|
+
self.stdout.write(f" ✅ Connectivity OK ({response_time:.0f}ms)")
|
195
|
+
else:
|
196
|
+
self.test_results[provider_name]['errors'].append("Connectivity test failed")
|
197
|
+
self.stdout.write(f" ❌ Connectivity failed")
|
198
|
+
|
199
|
+
except Exception as e:
|
200
|
+
error_msg = f"Connectivity test error: {e}"
|
201
|
+
self.test_results[provider_name]['errors'].append(error_msg)
|
202
|
+
self.stdout.write(f" ❌ Connectivity error: {e}")
|
203
|
+
|
204
|
+
def test_currencies(self, provider_name: str, provider):
|
205
|
+
"""Test provider currency support."""
|
206
|
+
self.stdout.write(" 💰 Testing currency support...")
|
207
|
+
|
208
|
+
try:
|
209
|
+
start_time = time.time()
|
210
|
+
|
211
|
+
# Get supported currencies from provider
|
212
|
+
if hasattr(provider, 'get_supported_currencies'):
|
213
|
+
supported_currencies = provider.get_supported_currencies()
|
214
|
+
else:
|
215
|
+
self.test_results[provider_name]['warnings'].append("Provider doesn't support currency listing")
|
216
|
+
self.stdout.write(" ⚠️ Provider doesn't support currency listing")
|
217
|
+
return
|
218
|
+
|
219
|
+
end_time = time.time()
|
220
|
+
response_time = (end_time - start_time) * 1000
|
221
|
+
|
222
|
+
self.test_results[provider_name]['performance']['currencies_ms'] = response_time
|
223
|
+
|
224
|
+
if isinstance(supported_currencies, list) and len(supported_currencies) > 0:
|
225
|
+
self.test_results[provider_name]['currencies'] = True
|
226
|
+
currency_count = len(supported_currencies)
|
227
|
+
self.stdout.write(f" ✅ {currency_count} currencies supported ({response_time:.0f}ms)")
|
228
|
+
|
229
|
+
if self.verbose:
|
230
|
+
# Show first few currencies
|
231
|
+
sample_currencies = supported_currencies[:5]
|
232
|
+
currency_codes = [c.get('code', str(c)) for c in sample_currencies]
|
233
|
+
self.stdout.write(f" Sample: {', '.join(currency_codes)}")
|
234
|
+
|
235
|
+
# Check if our database currencies are supported
|
236
|
+
our_currencies = Currency.objects.filter(is_active=True)[:10]
|
237
|
+
supported_codes = []
|
238
|
+
|
239
|
+
if isinstance(supported_currencies[0], dict):
|
240
|
+
supported_codes = [c.get('code', '').upper() for c in supported_currencies]
|
241
|
+
else:
|
242
|
+
supported_codes = [str(c).upper() for c in supported_currencies]
|
243
|
+
|
244
|
+
matching_count = 0
|
245
|
+
for currency in our_currencies:
|
246
|
+
if currency.code.upper() in supported_codes:
|
247
|
+
matching_count += 1
|
248
|
+
|
249
|
+
if matching_count > 0:
|
250
|
+
self.stdout.write(f" ✅ {matching_count}/{our_currencies.count()} of our currencies supported")
|
251
|
+
else:
|
252
|
+
self.test_results[provider_name]['warnings'].append("No matching currencies found")
|
253
|
+
self.stdout.write(" ⚠️ No matching currencies found")
|
254
|
+
|
255
|
+
else:
|
256
|
+
self.test_results[provider_name]['errors'].append("No currencies returned")
|
257
|
+
self.stdout.write(" ❌ No currencies returned")
|
258
|
+
|
259
|
+
except Exception as e:
|
260
|
+
error_msg = f"Currency test error: {e}"
|
261
|
+
self.test_results[provider_name]['errors'].append(error_msg)
|
262
|
+
self.stdout.write(f" ❌ Currency test error: {e}")
|
263
|
+
|
264
|
+
def test_payment_creation(self, provider_name: str, provider):
|
265
|
+
"""Test payment creation (creates actual test payment!)."""
|
266
|
+
self.stdout.write(" 💳 Testing payment creation...")
|
267
|
+
self.stdout.write(" ⚠️ WARNING: This creates an actual payment!")
|
268
|
+
|
269
|
+
try:
|
270
|
+
# Get a supported currency for testing
|
271
|
+
test_currency = self.get_test_currency(provider)
|
272
|
+
if not test_currency:
|
273
|
+
self.test_results[provider_name]['warnings'].append("No suitable test currency found")
|
274
|
+
self.stdout.write(" ⚠️ No suitable test currency found")
|
275
|
+
return
|
276
|
+
|
277
|
+
# Create test payment request
|
278
|
+
payment_request = PaymentCreateRequest(
|
279
|
+
amount_usd=self.options['test_amount'],
|
280
|
+
currency_code=test_currency,
|
281
|
+
description=f"Test payment from django-cfg ({timezone.now().isoformat()})",
|
282
|
+
callback_url="https://example.com/webhook/test",
|
283
|
+
success_url="https://example.com/success",
|
284
|
+
cancel_url="https://example.com/cancel"
|
285
|
+
)
|
286
|
+
|
287
|
+
start_time = time.time()
|
288
|
+
|
289
|
+
# Create payment
|
290
|
+
result = provider.create_payment(payment_request)
|
291
|
+
|
292
|
+
end_time = time.time()
|
293
|
+
response_time = (end_time - start_time) * 1000
|
294
|
+
|
295
|
+
self.test_results[provider_name]['performance']['payment_creation_ms'] = response_time
|
296
|
+
|
297
|
+
if hasattr(result, 'success') and result.success:
|
298
|
+
self.test_results[provider_name]['payment'] = True
|
299
|
+
payment_id = getattr(result, 'provider_payment_id', 'Unknown')
|
300
|
+
payment_url = getattr(result, 'payment_url', 'No URL')
|
301
|
+
|
302
|
+
self.stdout.write(f" ✅ Payment created ({response_time:.0f}ms)")
|
303
|
+
self.stdout.write(f" Payment ID: {payment_id}")
|
304
|
+
|
305
|
+
if self.verbose and payment_url != 'No URL':
|
306
|
+
self.stdout.write(f" Payment URL: {payment_url}")
|
307
|
+
|
308
|
+
# Test payment status check
|
309
|
+
if hasattr(provider, 'get_payment_status') and payment_id != 'Unknown':
|
310
|
+
try:
|
311
|
+
status_result = provider.get_payment_status(payment_id)
|
312
|
+
if hasattr(status_result, 'success') and status_result.success:
|
313
|
+
status = getattr(status_result, 'status', 'Unknown')
|
314
|
+
self.stdout.write(f" ✅ Status check OK: {status}")
|
315
|
+
else:
|
316
|
+
self.test_results[provider_name]['warnings'].append("Status check failed")
|
317
|
+
self.stdout.write(" ⚠️ Status check failed")
|
318
|
+
except Exception as e:
|
319
|
+
self.test_results[provider_name]['warnings'].append(f"Status check error: {e}")
|
320
|
+
self.stdout.write(f" ⚠️ Status check error: {e}")
|
321
|
+
|
322
|
+
else:
|
323
|
+
error = getattr(result, 'error', 'Unknown error')
|
324
|
+
self.test_results[provider_name]['errors'].append(f"Payment creation failed: {error}")
|
325
|
+
self.stdout.write(f" ❌ Payment creation failed: {error}")
|
326
|
+
|
327
|
+
except Exception as e:
|
328
|
+
error_msg = f"Payment creation error: {e}"
|
329
|
+
self.test_results[provider_name]['errors'].append(error_msg)
|
330
|
+
self.stdout.write(f" ❌ Payment creation error: {e}")
|
331
|
+
|
332
|
+
def get_test_currency(self, provider) -> Optional[str]:
|
333
|
+
"""Get a suitable currency for testing."""
|
334
|
+
# Common test currencies
|
335
|
+
test_currencies = ['BTC', 'ETH', 'USDT', 'LTC', 'USD']
|
336
|
+
|
337
|
+
try:
|
338
|
+
# Get supported currencies from provider
|
339
|
+
if hasattr(provider, 'get_supported_currencies'):
|
340
|
+
supported = provider.get_supported_currencies()
|
341
|
+
|
342
|
+
if isinstance(supported, list) and len(supported) > 0:
|
343
|
+
# Extract currency codes
|
344
|
+
if isinstance(supported[0], dict):
|
345
|
+
supported_codes = [c.get('code', '').upper() for c in supported]
|
346
|
+
else:
|
347
|
+
supported_codes = [str(c).upper() for c in supported]
|
348
|
+
|
349
|
+
# Find first matching test currency
|
350
|
+
for currency in test_currencies:
|
351
|
+
if currency in supported_codes:
|
352
|
+
return currency
|
353
|
+
|
354
|
+
# Fallback: return first supported currency
|
355
|
+
if supported_codes:
|
356
|
+
return supported_codes[0]
|
357
|
+
|
358
|
+
# Last resort: try BTC
|
359
|
+
return 'BTC'
|
360
|
+
|
361
|
+
except Exception:
|
362
|
+
return 'BTC'
|
363
|
+
|
364
|
+
def show_summary(self):
|
365
|
+
"""Display test summary."""
|
366
|
+
self.stdout.write(self.style.SUCCESS("📊 TEST SUMMARY"))
|
367
|
+
self.stdout.write("-" * 40)
|
368
|
+
|
369
|
+
total_providers = len(self.test_results)
|
370
|
+
successful_providers = 0
|
371
|
+
total_errors = 0
|
372
|
+
total_warnings = 0
|
373
|
+
|
374
|
+
for provider_name, results in self.test_results.items():
|
375
|
+
errors = len(results['errors'])
|
376
|
+
warnings = len(results['warnings'])
|
377
|
+
|
378
|
+
total_errors += errors
|
379
|
+
total_warnings += warnings
|
380
|
+
|
381
|
+
# Count as successful if no errors and at least one test passed
|
382
|
+
if errors == 0 and (results['connectivity'] or results['currencies']):
|
383
|
+
successful_providers += 1
|
384
|
+
|
385
|
+
# Show provider summary
|
386
|
+
status_icon = "✅" if errors == 0 else "❌"
|
387
|
+
self.stdout.write(f"{status_icon} {provider_name}:")
|
388
|
+
|
389
|
+
if results['connectivity']:
|
390
|
+
self.stdout.write(" ✅ Connectivity")
|
391
|
+
if results['currencies']:
|
392
|
+
self.stdout.write(" ✅ Currencies")
|
393
|
+
if results['payment']:
|
394
|
+
self.stdout.write(" ✅ Payment Creation")
|
395
|
+
|
396
|
+
if errors > 0:
|
397
|
+
self.stdout.write(f" ❌ {errors} error(s)")
|
398
|
+
if warnings > 0:
|
399
|
+
self.stdout.write(f" ⚠️ {warnings} warning(s)")
|
400
|
+
|
401
|
+
# Show performance metrics
|
402
|
+
if self.verbose and results['performance']:
|
403
|
+
perf = results['performance']
|
404
|
+
if 'connectivity_ms' in perf:
|
405
|
+
self.stdout.write(f" ⏱️ Connectivity: {perf['connectivity_ms']:.0f}ms")
|
406
|
+
if 'currencies_ms' in perf:
|
407
|
+
self.stdout.write(f" ⏱️ Currencies: {perf['currencies_ms']:.0f}ms")
|
408
|
+
if 'payment_creation_ms' in perf:
|
409
|
+
self.stdout.write(f" ⏱️ Payment: {perf['payment_creation_ms']:.0f}ms")
|
410
|
+
|
411
|
+
# Overall summary
|
412
|
+
self.stdout.write("")
|
413
|
+
self.stdout.write(f"Providers tested: {total_providers}")
|
414
|
+
self.stdout.write(f"Successful: {self.style.SUCCESS(successful_providers)}")
|
415
|
+
self.stdout.write(f"Failed: {self.style.ERROR(total_providers - successful_providers)}")
|
416
|
+
self.stdout.write(f"Total errors: {self.style.ERROR(total_errors)}")
|
417
|
+
self.stdout.write(f"Total warnings: {self.style.WARNING(total_warnings)}")
|
418
|
+
|
419
|
+
# Show completion time
|
420
|
+
self.stdout.write("")
|
421
|
+
self.stdout.write(f"Completed: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
422
|
+
|
423
|
+
# Recommendations
|
424
|
+
if total_errors > 0:
|
425
|
+
self.stdout.write("")
|
426
|
+
self.stdout.write(
|
427
|
+
self.style.WARNING("⚠️ Some providers have errors. Check configurations and network connectivity.")
|
428
|
+
)
|
429
|
+
|
430
|
+
if successful_providers == total_providers and total_errors == 0:
|
431
|
+
self.stdout.write("")
|
432
|
+
self.stdout.write(
|
433
|
+
self.style.SUCCESS("🎉 All providers tested successfully!")
|
434
|
+
)
|
@@ -44,8 +44,8 @@ class APIAccessMiddleware(MiddlewareMixin):
|
|
44
44
|
|
45
45
|
# Configuration from django-cfg
|
46
46
|
self.enabled = middleware_config['enabled']
|
47
|
-
self.
|
48
|
-
self.
|
47
|
+
self.protected_paths = middleware_config.get('protected_paths', [])
|
48
|
+
self.protected_patterns_raw = middleware_config.get('protected_patterns', [])
|
49
49
|
self.cache_timeout = middleware_config['cache_timeouts']['api_key']
|
50
50
|
|
51
51
|
# Default settings (can be overridden by Constance)
|
@@ -61,29 +61,32 @@ class APIAccessMiddleware(MiddlewareMixin):
|
|
61
61
|
|
62
62
|
except Exception as e:
|
63
63
|
logger.warning(f"Failed to load middleware config, using defaults: {e}")
|
64
|
-
# Fallback defaults
|
64
|
+
# Fallback defaults - whitelist approach
|
65
65
|
self.enabled = True
|
66
|
-
self.
|
67
|
-
|
66
|
+
self.protected_paths = [
|
67
|
+
'/api/admin/', # Admin API endpoints
|
68
|
+
'/api/private/', # Private API endpoints
|
69
|
+
'/api/secure/', # Secure API endpoints
|
70
|
+
]
|
71
|
+
self.protected_patterns_raw = [
|
72
|
+
r'^/api/admin/.*$', # All admin API endpoints
|
73
|
+
r'^/api/private/.*$', # All private API endpoints
|
74
|
+
r'^/api/secure/.*$', # All secure API endpoints
|
75
|
+
]
|
68
76
|
self.cache_timeout = 300
|
69
77
|
self.strict_mode = False
|
70
78
|
self.require_api_key = True
|
71
79
|
|
72
|
-
# Compile
|
73
|
-
self.
|
74
|
-
re.compile(pattern) for pattern in
|
75
|
-
r'^/api/payments/[^/]+/status/$',
|
76
|
-
r'^/api/webhooks/[^/]+/$',
|
77
|
-
r'^/api/payments/webhooks/(providers|health|stats)/$', # Admin webhook endpoints
|
78
|
-
r'^/api/currencies/(rates|supported|convert)/$',
|
79
|
-
]
|
80
|
+
# Compile protected path patterns
|
81
|
+
self.protected_patterns = [
|
82
|
+
re.compile(pattern) for pattern in self.protected_patterns_raw
|
80
83
|
]
|
81
84
|
|
82
85
|
logger.info(f"API Access Middleware initialized", extra={
|
83
86
|
'enabled': self.enabled,
|
84
87
|
'strict_mode': self.strict_mode,
|
85
88
|
'require_api_key': self.require_api_key,
|
86
|
-
'
|
89
|
+
'protected_paths': self.protected_paths
|
87
90
|
})
|
88
91
|
|
89
92
|
def process_request(self, request: HttpRequest) -> Optional[JsonResponse]:
|
@@ -95,8 +98,8 @@ class APIAccessMiddleware(MiddlewareMixin):
|
|
95
98
|
if not self.enabled:
|
96
99
|
return None
|
97
100
|
|
98
|
-
# Check if this path
|
99
|
-
if not self.
|
101
|
+
# Check if this path is protected (whitelist approach)
|
102
|
+
if not self._is_protected_path(request.path):
|
100
103
|
return None
|
101
104
|
|
102
105
|
# Start timing for performance monitoring
|
@@ -177,26 +180,24 @@ class APIAccessMiddleware(MiddlewareMixin):
|
|
177
180
|
# Graceful degradation: allow access but log the issue
|
178
181
|
return None
|
179
182
|
|
180
|
-
def
|
183
|
+
def _is_protected_path(self, path: str) -> bool:
|
181
184
|
"""
|
182
|
-
Check if the given path requires API authentication.
|
183
|
-
"""
|
184
|
-
# Check if path starts with API prefix
|
185
|
-
requires_auth = any(path.startswith(prefix) for prefix in self.api_prefixes)
|
186
|
-
|
187
|
-
if not requires_auth:
|
188
|
-
return False
|
185
|
+
Check if the given path is protected and requires API authentication.
|
189
186
|
|
190
|
-
|
191
|
-
|
192
|
-
|
187
|
+
Whitelist approach: only paths explicitly listed as protected require API key.
|
188
|
+
"""
|
189
|
+
# Check exact protected paths
|
190
|
+
for protected_path in self.protected_paths:
|
191
|
+
if path.startswith(protected_path):
|
192
|
+
return True
|
193
193
|
|
194
|
-
# Check
|
195
|
-
for pattern in self.
|
194
|
+
# Check protected patterns
|
195
|
+
for pattern in self.protected_patterns:
|
196
196
|
if pattern.match(path):
|
197
|
-
return
|
197
|
+
return True
|
198
198
|
|
199
|
-
|
199
|
+
# Path is not protected - no API key required
|
200
|
+
return False
|
200
201
|
|
201
202
|
def _extract_api_key(self, request: HttpRequest) -> Optional[str]:
|
202
203
|
"""
|
@@ -300,7 +301,7 @@ class APIAccessMiddleware(MiddlewareMixin):
|
|
300
301
|
user=api_key.user,
|
301
302
|
status=Subscription.SubscriptionStatus.ACTIVE,
|
302
303
|
expires_at__gt=timezone.now()
|
303
|
-
).
|
304
|
+
).prefetch_related('endpoint_groups')
|
304
305
|
|
305
306
|
if not active_subscriptions.exists():
|
306
307
|
return {
|
@@ -319,8 +320,8 @@ class APIAccessMiddleware(MiddlewareMixin):
|
|
319
320
|
'allowed': True,
|
320
321
|
'subscription_id': str(subscription.id),
|
321
322
|
'tier': subscription.tier,
|
322
|
-
'
|
323
|
-
'requests_remaining': subscription
|
323
|
+
'tier_name': subscription.tier,
|
324
|
+
'requests_remaining': 'unlimited', # TODO: Implement rate limiting per subscription
|
324
325
|
'expires_at': subscription.expires_at.isoformat() if subscription.expires_at else None
|
325
326
|
}
|
326
327
|
|
@@ -239,6 +239,9 @@ class Transaction(UUIDTimestampedModel):
|
|
239
239
|
|
240
240
|
def save(self, *args, **kwargs):
|
241
241
|
"""Override save to ensure immutability."""
|
242
|
-
|
243
|
-
|
242
|
+
# Only prevent updates, not creation
|
243
|
+
if self.pk and not kwargs.get('force_insert', False):
|
244
|
+
# Check if this is actually an update (record exists in DB)
|
245
|
+
if Transaction.objects.filter(pk=self.pk).exists():
|
246
|
+
raise ValidationError("Transactions are immutable and cannot be modified")
|
244
247
|
super().save(*args, **kwargs)
|
@@ -93,6 +93,10 @@ class APIKeyManager(models.Manager):
|
|
93
93
|
"""Get API keys expiring soon."""
|
94
94
|
return self.get_queryset().expiring_soon(days)
|
95
95
|
|
96
|
+
def by_user(self, user):
|
97
|
+
"""Get API keys by user."""
|
98
|
+
return self.get_queryset().by_user(user)
|
99
|
+
|
96
100
|
# Business logic methods
|
97
101
|
def increment_api_key_usage(self, api_key_id, ip_address=None):
|
98
102
|
"""
|
@@ -230,7 +234,7 @@ class APIKeyManager(models.Manager):
|
|
230
234
|
logger.info(f"Created API key for user", extra={
|
231
235
|
'api_key_id': str(api_key.id),
|
232
236
|
'user_id': user.id,
|
233
|
-
'
|
237
|
+
'key_name': name,
|
234
238
|
'expires_in_days': expires_in_days
|
235
239
|
})
|
236
240
|
|
@@ -239,7 +243,7 @@ class APIKeyManager(models.Manager):
|
|
239
243
|
except Exception as e:
|
240
244
|
logger.error(f"Failed to create API key: {e}", extra={
|
241
245
|
'user_id': user.id,
|
242
|
-
'
|
246
|
+
'key_name': name
|
243
247
|
})
|
244
248
|
raise
|
245
249
|
|
@@ -4,7 +4,7 @@ Balance and transaction managers for the Universal Payment System v2.0.
|
|
4
4
|
Optimized querysets and managers for balance and transaction operations.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from django.db import models
|
7
|
+
from django.db import models, transaction
|
8
8
|
from django.utils import timezone
|
9
9
|
from django_cfg.modules.django_logger import get_logger
|
10
10
|
|
@@ -62,7 +62,7 @@ class UserBalanceManager(models.Manager):
|
|
62
62
|
# Get or create balance
|
63
63
|
balance = self.get_or_create_for_user(user)
|
64
64
|
|
65
|
-
with
|
65
|
+
with transaction.atomic():
|
66
66
|
# Update balance
|
67
67
|
balance.balance_usd += amount
|
68
68
|
balance.total_deposited += amount
|
@@ -122,7 +122,7 @@ class UserBalanceManager(models.Manager):
|
|
122
122
|
if amount > balance.balance_usd:
|
123
123
|
raise ValueError(f"Insufficient balance: ${balance.balance_usd:.2f} < ${amount:.2f}")
|
124
124
|
|
125
|
-
with
|
125
|
+
with transaction.atomic():
|
126
126
|
# Update balance
|
127
127
|
balance.balance_usd -= amount
|
128
128
|
balance.total_spent += amount
|
@@ -244,6 +244,11 @@ class PaymentManager(models.Manager):
|
|
244
244
|
"""Get active payments."""
|
245
245
|
return self.get_queryset().active()
|
246
246
|
|
247
|
+
# User-based methods
|
248
|
+
def by_user(self, user):
|
249
|
+
"""Get payments by user."""
|
250
|
+
return self.get_queryset().by_user(user)
|
251
|
+
|
247
252
|
# Provider-based methods
|
248
253
|
def by_provider(self, provider):
|
249
254
|
"""Get payments by provider."""
|
@@ -544,7 +544,7 @@ class SubscriptionManager(models.Manager):
|
|
544
544
|
else:
|
545
545
|
subscription = subscription_id
|
546
546
|
|
547
|
-
subscription.status =
|
547
|
+
subscription.status = self.model.SubscriptionStatus.CANCELLED
|
548
548
|
subscription.save(update_fields=['status', 'updated_at'])
|
549
549
|
|
550
550
|
logger.info(f"Subscription cancelled", extra={
|
@@ -580,7 +580,7 @@ class SubscriptionManager(models.Manager):
|
|
580
580
|
|
581
581
|
from datetime import timedelta
|
582
582
|
|
583
|
-
if subscription.
|
583
|
+
if subscription.expires_at <= timezone.now():
|
584
584
|
# If expired, start from now
|
585
585
|
subscription.starts_at = timezone.now()
|
586
586
|
subscription.expires_at = subscription.starts_at + timedelta(days=duration_days)
|
@@ -588,7 +588,7 @@ class SubscriptionManager(models.Manager):
|
|
588
588
|
# If not expired, extend from current expiration
|
589
589
|
subscription.expires_at += timedelta(days=duration_days)
|
590
590
|
|
591
|
-
subscription.status =
|
591
|
+
subscription.status = self.model.SubscriptionStatus.ACTIVE
|
592
592
|
subscription.save(update_fields=['starts_at', 'expires_at', 'status', 'updated_at'])
|
593
593
|
|
594
594
|
logger.info(f"Subscription renewed", extra={
|