django-cfg 1.2.22__py3-none-any.whl → 1.2.23__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/__init__.py +23 -0
- django_cfg/apps/payments/admin/api_keys_admin.py +347 -0
- django_cfg/apps/payments/admin/balance_admin.py +434 -0
- django_cfg/apps/payments/admin/currencies_admin.py +186 -0
- django_cfg/apps/payments/admin/filters.py +259 -0
- django_cfg/apps/payments/admin/payments_admin.py +142 -0
- django_cfg/apps/payments/admin/subscriptions_admin.py +227 -0
- django_cfg/apps/payments/admin/tariffs_admin.py +199 -0
- django_cfg/apps/payments/config/__init__.py +87 -0
- django_cfg/apps/payments/config/module.py +162 -0
- django_cfg/apps/payments/config/providers.py +93 -0
- django_cfg/apps/payments/config/settings.py +136 -0
- django_cfg/apps/payments/config/utils.py +198 -0
- django_cfg/apps/payments/decorators.py +291 -0
- django_cfg/apps/payments/middleware/api_access.py +261 -0
- django_cfg/apps/payments/middleware/rate_limiting.py +216 -0
- django_cfg/apps/payments/middleware/usage_tracking.py +296 -0
- django_cfg/apps/payments/migrations/0001_initial.py +32 -11
- django_cfg/apps/payments/models/__init__.py +18 -0
- django_cfg/apps/payments/models/api_keys.py +2 -2
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/base.py +16 -0
- django_cfg/apps/payments/models/events.py +2 -2
- django_cfg/apps/payments/models/payments.py +2 -2
- django_cfg/apps/payments/models/subscriptions.py +2 -2
- django_cfg/apps/payments/services/__init__.py +58 -7
- django_cfg/apps/payments/services/billing/__init__.py +8 -0
- django_cfg/apps/payments/services/cache/__init__.py +15 -0
- django_cfg/apps/payments/services/cache/base.py +30 -0
- django_cfg/apps/payments/services/cache/simple_cache.py +135 -0
- django_cfg/apps/payments/services/core/__init__.py +17 -0
- django_cfg/apps/payments/services/core/balance_service.py +449 -0
- django_cfg/apps/payments/services/core/payment_service.py +393 -0
- django_cfg/apps/payments/services/core/subscription_service.py +616 -0
- django_cfg/apps/payments/services/internal_types.py +266 -0
- django_cfg/apps/payments/services/middleware/__init__.py +8 -0
- django_cfg/apps/payments/services/providers/__init__.py +19 -0
- django_cfg/apps/payments/services/providers/base.py +137 -0
- django_cfg/apps/payments/services/providers/cryptapi.py +262 -0
- django_cfg/apps/payments/services/providers/nowpayments.py +293 -0
- django_cfg/apps/payments/services/providers/registry.py +99 -0
- django_cfg/apps/payments/services/validators/__init__.py +8 -0
- django_cfg/apps/payments/signals/__init__.py +13 -0
- django_cfg/apps/payments/signals/api_key_signals.py +150 -0
- django_cfg/apps/payments/signals/payment_signals.py +127 -0
- django_cfg/apps/payments/signals/subscription_signals.py +196 -0
- django_cfg/apps/payments/urls.py +5 -5
- django_cfg/apps/payments/utils/__init__.py +42 -0
- django_cfg/apps/payments/utils/config_utils.py +243 -0
- django_cfg/apps/payments/utils/middleware_utils.py +228 -0
- django_cfg/apps/payments/utils/validation_utils.py +94 -0
- django_cfg/apps/support/signals.py +16 -4
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/models/revolution.py +1 -1
- django_cfg/modules/base.py +1 -1
- django_cfg/modules/django_email.py +42 -4
- django_cfg/modules/django_unfold/dashboard.py +20 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/METADATA +2 -1
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/RECORD +63 -26
- django_cfg/apps/payments/services/base.py +0 -68
- django_cfg/apps/payments/services/nowpayments.py +0 -78
- django_cfg/apps/payments/services/providers.py +0 -77
- django_cfg/apps/payments/services/redis_service.py +0 -215
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
"""
|
2
|
+
🔄 Universal Subscription Signals
|
3
|
+
|
4
|
+
Automatic subscription management and lifecycle handling via Django signals.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.db.models.signals import post_save, pre_save, post_delete
|
8
|
+
from django.dispatch import receiver
|
9
|
+
from django.db import transaction
|
10
|
+
from django.utils import timezone
|
11
|
+
from datetime import timedelta
|
12
|
+
import logging
|
13
|
+
|
14
|
+
from ..models import Subscription, EndpointGroup, UserBalance, Transaction
|
15
|
+
from ..services.cache import SimpleCache
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
@receiver(pre_save, sender=Subscription)
|
21
|
+
def store_original_subscription_status(sender, instance, **kwargs):
|
22
|
+
"""Store original subscription status for change detection."""
|
23
|
+
if instance.pk:
|
24
|
+
try:
|
25
|
+
old_instance = Subscription.objects.get(pk=instance.pk)
|
26
|
+
instance._original_status = old_instance.status
|
27
|
+
instance._original_expires_at = old_instance.expires_at
|
28
|
+
except Subscription.DoesNotExist:
|
29
|
+
instance._original_status = None
|
30
|
+
instance._original_expires_at = None
|
31
|
+
|
32
|
+
|
33
|
+
@receiver(post_save, sender=Subscription)
|
34
|
+
def process_subscription_status_changes(sender, instance, created, **kwargs):
|
35
|
+
"""Process subscription status changes and handle lifecycle events."""
|
36
|
+
if created:
|
37
|
+
logger.info(
|
38
|
+
f"New subscription created: {instance.endpoint_group.name} "
|
39
|
+
f"for user {instance.user.email} (expires: {instance.expires_at})"
|
40
|
+
)
|
41
|
+
_clear_user_cache(instance.user.id)
|
42
|
+
return
|
43
|
+
|
44
|
+
# Check if status changed
|
45
|
+
if hasattr(instance, '_original_status'):
|
46
|
+
old_status = instance._original_status
|
47
|
+
new_status = instance.status
|
48
|
+
|
49
|
+
if old_status != new_status:
|
50
|
+
logger.info(
|
51
|
+
f"Subscription status changed: {instance.endpoint_group.name} "
|
52
|
+
f"for user {instance.user.email} - {old_status} → {new_status}"
|
53
|
+
)
|
54
|
+
|
55
|
+
# Handle specific status changes
|
56
|
+
if new_status == Subscription.SubscriptionStatus.ACTIVE:
|
57
|
+
_handle_subscription_activation(instance)
|
58
|
+
elif new_status == Subscription.SubscriptionStatus.CANCELLED:
|
59
|
+
_handle_subscription_cancellation(instance)
|
60
|
+
elif new_status == Subscription.SubscriptionStatus.EXPIRED:
|
61
|
+
_handle_subscription_expiration(instance)
|
62
|
+
|
63
|
+
_clear_user_cache(instance.user.id)
|
64
|
+
|
65
|
+
|
66
|
+
@receiver(post_save, sender=Subscription)
|
67
|
+
def handle_subscription_renewal(sender, instance, created, **kwargs):
|
68
|
+
"""Handle subscription renewal and billing."""
|
69
|
+
if created or not hasattr(instance, '_original_expires_at'):
|
70
|
+
return
|
71
|
+
|
72
|
+
old_expires_at = instance._original_expires_at
|
73
|
+
new_expires_at = instance.expires_at
|
74
|
+
|
75
|
+
# Check if subscription was renewed (expires_at extended)
|
76
|
+
if old_expires_at and new_expires_at and new_expires_at > old_expires_at:
|
77
|
+
logger.info(
|
78
|
+
f"Subscription renewed: {instance.endpoint_group.name} "
|
79
|
+
f"for user {instance.user.email} - extended to {new_expires_at}"
|
80
|
+
)
|
81
|
+
_clear_user_cache(instance.user.id)
|
82
|
+
|
83
|
+
|
84
|
+
@receiver(post_delete, sender=Subscription)
|
85
|
+
def log_subscription_deletion(sender, instance, **kwargs):
|
86
|
+
"""Log subscription deletions for audit purposes."""
|
87
|
+
logger.warning(
|
88
|
+
f"Subscription deleted: {instance.endpoint_group.name} "
|
89
|
+
f"for user {instance.user.email} - Status was: {instance.status}"
|
90
|
+
)
|
91
|
+
_clear_user_cache(instance.user.id)
|
92
|
+
|
93
|
+
|
94
|
+
@receiver(post_save, sender=EndpointGroup)
|
95
|
+
def log_endpoint_group_changes(sender, instance, created, **kwargs):
|
96
|
+
"""Log endpoint group changes that may affect subscriptions."""
|
97
|
+
if created:
|
98
|
+
logger.info(f"New endpoint group created: {instance.name}")
|
99
|
+
else:
|
100
|
+
# Check if important fields changed
|
101
|
+
if instance.tracker.has_changed('is_active'):
|
102
|
+
logger.warning(
|
103
|
+
f"Endpoint group activity changed: {instance.name} "
|
104
|
+
f"- active: {instance.is_active}"
|
105
|
+
)
|
106
|
+
# Clear cache for all users with subscriptions to this group
|
107
|
+
_clear_endpoint_group_cache(instance)
|
108
|
+
|
109
|
+
|
110
|
+
def _handle_subscription_activation(subscription: Subscription):
|
111
|
+
"""Handle subscription activation logic."""
|
112
|
+
try:
|
113
|
+
# Reset usage counters
|
114
|
+
subscription.usage_current = 0
|
115
|
+
|
116
|
+
# Set next billing date
|
117
|
+
if not subscription.next_billing:
|
118
|
+
subscription.next_billing = timezone.now() + timedelta(days=30) # Monthly by default
|
119
|
+
|
120
|
+
subscription.save(update_fields=['usage_current', 'next_billing'])
|
121
|
+
|
122
|
+
logger.info(f"Subscription activated: {subscription.endpoint_group.name} for {subscription.user.email}")
|
123
|
+
|
124
|
+
except Exception as e:
|
125
|
+
logger.error(f"Error handling subscription activation: {e}")
|
126
|
+
|
127
|
+
|
128
|
+
def _handle_subscription_cancellation(subscription: Subscription):
|
129
|
+
"""Handle subscription cancellation logic."""
|
130
|
+
try:
|
131
|
+
# Mark as cancelled
|
132
|
+
subscription.cancelled_at = timezone.now()
|
133
|
+
subscription.save(update_fields=['cancelled_at'])
|
134
|
+
|
135
|
+
logger.info(f"Subscription cancelled: {subscription.endpoint_group.name} for {subscription.user.email}")
|
136
|
+
|
137
|
+
except Exception as e:
|
138
|
+
logger.error(f"Error handling subscription cancellation: {e}")
|
139
|
+
|
140
|
+
|
141
|
+
def _handle_subscription_expiration(subscription: Subscription):
|
142
|
+
"""Handle subscription expiration logic."""
|
143
|
+
try:
|
144
|
+
# Mark as expired
|
145
|
+
subscription.expired_at = timezone.now()
|
146
|
+
subscription.save(update_fields=['expired_at'])
|
147
|
+
|
148
|
+
logger.info(f"Subscription expired: {subscription.endpoint_group.name} for {subscription.user.email}")
|
149
|
+
|
150
|
+
except Exception as e:
|
151
|
+
logger.error(f"Error handling subscription expiration: {e}")
|
152
|
+
|
153
|
+
|
154
|
+
def _clear_user_cache(user_id: int):
|
155
|
+
"""Clear cache for specific user."""
|
156
|
+
try:
|
157
|
+
cache = SimpleCache("subscriptions")
|
158
|
+
cache_keys = [
|
159
|
+
f"access:{user_id}",
|
160
|
+
f"subscriptions:{user_id}",
|
161
|
+
f"user_summary:{user_id}",
|
162
|
+
]
|
163
|
+
|
164
|
+
for key in cache_keys:
|
165
|
+
cache.delete(key)
|
166
|
+
|
167
|
+
except Exception as e:
|
168
|
+
logger.warning(f"Failed to clear cache for user {user_id}: {e}")
|
169
|
+
|
170
|
+
|
171
|
+
def _clear_endpoint_group_cache(endpoint_group: EndpointGroup):
|
172
|
+
"""Clear cache for all users with subscriptions to this endpoint group."""
|
173
|
+
try:
|
174
|
+
# Get all users with active subscriptions to this group
|
175
|
+
user_ids = Subscription.objects.filter(
|
176
|
+
endpoint_group=endpoint_group,
|
177
|
+
status=Subscription.SubscriptionStatus.ACTIVE
|
178
|
+
).values_list('user_id', flat=True)
|
179
|
+
|
180
|
+
for user_id in user_ids:
|
181
|
+
_clear_user_cache(user_id)
|
182
|
+
|
183
|
+
except Exception as e:
|
184
|
+
logger.warning(f"Failed to clear cache for endpoint group {endpoint_group.name}: {e}")
|
185
|
+
|
186
|
+
|
187
|
+
@receiver(post_save, sender=Subscription)
|
188
|
+
def update_usage_statistics(sender, instance, created, **kwargs):
|
189
|
+
"""Update usage statistics when subscription is modified."""
|
190
|
+
if not created and hasattr(instance, '_original_status'):
|
191
|
+
# Only update stats if usage-related fields might have changed
|
192
|
+
if instance.usage_current != getattr(instance, '_original_usage_current', instance.usage_current):
|
193
|
+
logger.debug(
|
194
|
+
f"Usage updated for subscription {instance.endpoint_group.name}: "
|
195
|
+
f"{instance.usage_current} requests"
|
196
|
+
)
|
django_cfg/apps/payments/urls.py
CHANGED
@@ -66,13 +66,13 @@ generic_patterns = [
|
|
66
66
|
|
67
67
|
urlpatterns = [
|
68
68
|
# Include all router URLs
|
69
|
-
path('
|
69
|
+
path('', include(router.urls)),
|
70
70
|
|
71
71
|
# Include nested router URLs
|
72
|
-
path('
|
73
|
-
path('
|
74
|
-
path('
|
72
|
+
path('', include(payments_router.urls)),
|
73
|
+
path('', include(subscriptions_router.urls)),
|
74
|
+
path('', include(apikeys_router.urls)),
|
75
75
|
|
76
76
|
# Include generic API endpoints
|
77
|
-
path('
|
77
|
+
path('', include(generic_patterns)),
|
78
78
|
]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
"""
|
2
|
+
Utilities for universal payments.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .middleware_utils import get_client_ip, is_api_request, extract_api_key
|
6
|
+
# from .billing_utils import calculate_usage_cost, create_billing_transaction # TODO: Implement when needed
|
7
|
+
from .validation_utils import validate_api_key, check_subscription_access
|
8
|
+
|
9
|
+
# Configuration utilities
|
10
|
+
from .config_utils import (
|
11
|
+
PaymentsConfigUtil,
|
12
|
+
RedisConfigHelper,
|
13
|
+
CacheConfigHelper,
|
14
|
+
ProviderConfigHelper,
|
15
|
+
get_payments_config,
|
16
|
+
is_payments_enabled,
|
17
|
+
is_debug_mode
|
18
|
+
)
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
# Middleware utilities
|
22
|
+
'get_client_ip',
|
23
|
+
'is_api_request',
|
24
|
+
'extract_api_key',
|
25
|
+
|
26
|
+
# Billing utilities (TODO: Implement when needed)
|
27
|
+
# 'calculate_usage_cost',
|
28
|
+
# 'create_billing_transaction',
|
29
|
+
|
30
|
+
# Validation utilities
|
31
|
+
'validate_api_key',
|
32
|
+
'check_subscription_access',
|
33
|
+
|
34
|
+
# Configuration utilities
|
35
|
+
'PaymentsConfigUtil',
|
36
|
+
'RedisConfigHelper',
|
37
|
+
'CacheConfigHelper',
|
38
|
+
'ProviderConfigHelper',
|
39
|
+
'get_payments_config',
|
40
|
+
'is_payments_enabled',
|
41
|
+
'is_debug_mode',
|
42
|
+
]
|
@@ -0,0 +1,243 @@
|
|
1
|
+
"""
|
2
|
+
Configuration utilities for payments module.
|
3
|
+
|
4
|
+
Universal utilities for working with django-cfg settings and configuration.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Optional, Dict, Any, Type
|
9
|
+
from django.conf import settings
|
10
|
+
|
11
|
+
from django_cfg.modules.base import BaseCfgModule
|
12
|
+
from ..config.settings import PaymentsSettings
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
class PaymentsConfigMixin:
|
18
|
+
"""Mixin for accessing payments configuration through django-cfg."""
|
19
|
+
|
20
|
+
_payments_config_cache: Optional[PaymentsSettings] = None
|
21
|
+
_config_module: Optional[BaseCfgModule] = None
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def get_payments_config(cls) -> PaymentsSettings:
|
25
|
+
"""Get payments configuration from django-cfg."""
|
26
|
+
if cls._payments_config_cache is None:
|
27
|
+
cls._payments_config_cache = cls._load_payments_config()
|
28
|
+
return cls._payments_config_cache
|
29
|
+
|
30
|
+
@classmethod
|
31
|
+
def _load_payments_config(cls) -> PaymentsSettings:
|
32
|
+
"""Load payments configuration using BaseCfgModule."""
|
33
|
+
try:
|
34
|
+
if cls._config_module is None:
|
35
|
+
from ..config.module import PaymentsCfgModule
|
36
|
+
cls._config_module = PaymentsCfgModule()
|
37
|
+
|
38
|
+
return cls._config_module.get_config()
|
39
|
+
except Exception as e:
|
40
|
+
logger.warning(f"Failed to load payments config: {e}")
|
41
|
+
return PaymentsSettings()
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def reset_config_cache(cls):
|
45
|
+
"""Reset configuration cache."""
|
46
|
+
cls._payments_config_cache = None
|
47
|
+
if cls._config_module:
|
48
|
+
cls._config_module.reset_cache()
|
49
|
+
|
50
|
+
|
51
|
+
class RedisConfigHelper(PaymentsConfigMixin):
|
52
|
+
"""Helper for Redis configuration."""
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def get_redis_config(cls) -> Dict[str, Any]:
|
56
|
+
"""Get Redis configuration for payments."""
|
57
|
+
config = cls.get_payments_config()
|
58
|
+
|
59
|
+
# Default Redis settings
|
60
|
+
redis_config = {
|
61
|
+
'host': 'localhost',
|
62
|
+
'port': 6379,
|
63
|
+
'db': 0,
|
64
|
+
'decode_responses': True,
|
65
|
+
'socket_timeout': 5,
|
66
|
+
'socket_connect_timeout': 5,
|
67
|
+
'retry_on_timeout': True,
|
68
|
+
'health_check_interval': 30,
|
69
|
+
}
|
70
|
+
|
71
|
+
# Try to get Redis settings from Django CACHES
|
72
|
+
django_cache = getattr(settings, 'CACHES', {}).get('default', {})
|
73
|
+
if 'redis' in django_cache.get('BACKEND', '').lower():
|
74
|
+
location = django_cache.get('LOCATION', '')
|
75
|
+
if location.startswith('redis://'):
|
76
|
+
# Parse redis://host:port/db format
|
77
|
+
try:
|
78
|
+
# Simple parsing for redis://host:port/db
|
79
|
+
parts = location.replace('redis://', '').split('/')
|
80
|
+
host_port = parts[0].split(':')
|
81
|
+
redis_config['host'] = host_port[0]
|
82
|
+
if len(host_port) > 1:
|
83
|
+
redis_config['port'] = int(host_port[1])
|
84
|
+
if len(parts) > 1:
|
85
|
+
redis_config['db'] = int(parts[1])
|
86
|
+
except (ValueError, IndexError) as e:
|
87
|
+
logger.warning(f"Failed to parse Redis URL {location}: {e}")
|
88
|
+
|
89
|
+
# Override with payments-specific Redis config if available
|
90
|
+
if hasattr(config, 'redis') and config.redis:
|
91
|
+
redis_config.update(config.redis.dict())
|
92
|
+
|
93
|
+
return redis_config
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def is_redis_available(cls) -> bool:
|
97
|
+
"""Check if Redis is available and configured."""
|
98
|
+
try:
|
99
|
+
import redis
|
100
|
+
config = cls.get_redis_config()
|
101
|
+
client = redis.Redis(**config)
|
102
|
+
client.ping()
|
103
|
+
return True
|
104
|
+
except Exception as e:
|
105
|
+
logger.debug(f"Redis not available: {e}")
|
106
|
+
return False
|
107
|
+
|
108
|
+
|
109
|
+
class CacheConfigHelper(PaymentsConfigMixin):
|
110
|
+
"""Helper for cache configuration."""
|
111
|
+
|
112
|
+
@classmethod
|
113
|
+
def get_cache_backend_type(cls) -> str:
|
114
|
+
"""Get Django cache backend type."""
|
115
|
+
django_cache = getattr(settings, 'CACHES', {}).get('default', {})
|
116
|
+
backend = django_cache.get('BACKEND', '').lower()
|
117
|
+
|
118
|
+
if 'redis' in backend:
|
119
|
+
return 'redis'
|
120
|
+
elif 'memcached' in backend:
|
121
|
+
return 'memcached'
|
122
|
+
elif 'database' in backend:
|
123
|
+
return 'database'
|
124
|
+
elif 'dummy' in backend:
|
125
|
+
return 'dummy'
|
126
|
+
else:
|
127
|
+
return 'unknown'
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def is_cache_enabled(cls) -> bool:
|
131
|
+
"""Check if cache is properly configured (not dummy)."""
|
132
|
+
return cls.get_cache_backend_type() != 'dummy'
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
def get_cache_timeout(cls, operation: str) -> int:
|
136
|
+
"""Get cache timeout for specific operation."""
|
137
|
+
config = cls.get_payments_config()
|
138
|
+
|
139
|
+
timeouts = {
|
140
|
+
'api_key': 300, # 5 minutes
|
141
|
+
'rate_limit': 3600, # 1 hour
|
142
|
+
'session': 1800, # 30 minutes
|
143
|
+
'default': 600 # 10 minutes
|
144
|
+
}
|
145
|
+
|
146
|
+
# Override with config if available
|
147
|
+
if hasattr(config, 'cache_timeouts') and config.cache_timeouts:
|
148
|
+
timeouts.update(config.cache_timeouts)
|
149
|
+
|
150
|
+
return timeouts.get(operation, timeouts['default'])
|
151
|
+
|
152
|
+
|
153
|
+
class ProviderConfigHelper(PaymentsConfigMixin):
|
154
|
+
"""Helper for payment provider configuration."""
|
155
|
+
|
156
|
+
@classmethod
|
157
|
+
def get_enabled_providers(cls) -> list:
|
158
|
+
"""Get list of enabled payment providers."""
|
159
|
+
config = cls.get_payments_config()
|
160
|
+
if not config.enabled:
|
161
|
+
return []
|
162
|
+
|
163
|
+
enabled = []
|
164
|
+
if hasattr(config, 'providers') and config.providers:
|
165
|
+
for provider_name, provider_config in config.providers.items():
|
166
|
+
if provider_config and cls._is_provider_properly_configured(provider_name, provider_config):
|
167
|
+
enabled.append(provider_name)
|
168
|
+
|
169
|
+
return enabled
|
170
|
+
|
171
|
+
@classmethod
|
172
|
+
def get_provider_config(cls, provider_name: str) -> Optional[Any]:
|
173
|
+
"""Get configuration for specific provider."""
|
174
|
+
config = cls.get_payments_config()
|
175
|
+
if not config.enabled or not hasattr(config, 'providers'):
|
176
|
+
return None
|
177
|
+
|
178
|
+
return config.providers.get(provider_name)
|
179
|
+
|
180
|
+
@classmethod
|
181
|
+
def is_provider_enabled(cls, provider_name: str) -> bool:
|
182
|
+
"""Check if specific provider is enabled and configured."""
|
183
|
+
return provider_name in cls.get_enabled_providers()
|
184
|
+
|
185
|
+
@classmethod
|
186
|
+
def _is_provider_properly_configured(cls, provider_name: str, provider_config: Any) -> bool:
|
187
|
+
"""Check if provider configuration is complete."""
|
188
|
+
if not provider_config:
|
189
|
+
return False
|
190
|
+
|
191
|
+
# Basic validation - each provider should have api_key
|
192
|
+
if not hasattr(provider_config, 'api_key') or not provider_config.api_key:
|
193
|
+
return False
|
194
|
+
|
195
|
+
# Provider-specific validations
|
196
|
+
if provider_name == 'nowpayments':
|
197
|
+
return True # api_key is sufficient
|
198
|
+
elif provider_name == 'stripe':
|
199
|
+
return True # api_key is sufficient
|
200
|
+
elif provider_name == 'cryptapi':
|
201
|
+
return hasattr(provider_config, 'own_address') and provider_config.own_address
|
202
|
+
|
203
|
+
return True
|
204
|
+
|
205
|
+
|
206
|
+
class PaymentsConfigUtil:
|
207
|
+
"""
|
208
|
+
Universal utility for payments configuration.
|
209
|
+
|
210
|
+
Combines all config helpers into one convenient interface.
|
211
|
+
"""
|
212
|
+
|
213
|
+
redis = RedisConfigHelper
|
214
|
+
cache = CacheConfigHelper
|
215
|
+
providers = ProviderConfigHelper
|
216
|
+
|
217
|
+
@staticmethod
|
218
|
+
def get_config() -> PaymentsSettings:
|
219
|
+
"""Get payments configuration."""
|
220
|
+
return PaymentsConfigMixin.get_payments_config()
|
221
|
+
|
222
|
+
@staticmethod
|
223
|
+
def is_payments_enabled() -> bool:
|
224
|
+
"""Check if payments module is enabled."""
|
225
|
+
config = PaymentsConfigMixin.get_payments_config()
|
226
|
+
return config.enabled
|
227
|
+
|
228
|
+
@staticmethod
|
229
|
+
def is_debug_mode() -> bool:
|
230
|
+
"""Check if payments module is in debug mode."""
|
231
|
+
config = PaymentsConfigMixin.get_payments_config()
|
232
|
+
return getattr(config, 'debug_mode', False)
|
233
|
+
|
234
|
+
@staticmethod
|
235
|
+
def reset_all_caches():
|
236
|
+
"""Reset all configuration caches."""
|
237
|
+
PaymentsConfigMixin.reset_config_cache()
|
238
|
+
|
239
|
+
|
240
|
+
# Convenience exports
|
241
|
+
get_payments_config = PaymentsConfigUtil.get_config
|
242
|
+
is_payments_enabled = PaymentsConfigUtil.is_payments_enabled
|
243
|
+
is_debug_mode = PaymentsConfigUtil.is_debug_mode
|