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,199 @@
|
|
1
|
+
"""
|
2
|
+
Admin interface for tariffs.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.contrib import admin
|
6
|
+
from django.utils.html import format_html
|
7
|
+
from django.contrib.humanize.templatetags.humanize import naturaltime
|
8
|
+
from unfold.admin import ModelAdmin
|
9
|
+
from unfold.decorators import display
|
10
|
+
|
11
|
+
from ..models import Tariff, TariffEndpointGroup
|
12
|
+
|
13
|
+
|
14
|
+
@admin.register(Tariff)
|
15
|
+
class TariffAdmin(ModelAdmin):
|
16
|
+
"""Admin interface for tariffs."""
|
17
|
+
|
18
|
+
list_display = [
|
19
|
+
'tariff_display',
|
20
|
+
'price_display',
|
21
|
+
'limit_display',
|
22
|
+
'status_display',
|
23
|
+
'endpoint_groups_count',
|
24
|
+
'created_at_display'
|
25
|
+
]
|
26
|
+
|
27
|
+
list_display_links = ['tariff_display']
|
28
|
+
|
29
|
+
search_fields = ['name', 'display_name', 'description']
|
30
|
+
|
31
|
+
list_filter = ['is_active', 'created_at']
|
32
|
+
|
33
|
+
readonly_fields = ['created_at', 'updated_at']
|
34
|
+
|
35
|
+
fieldsets = [
|
36
|
+
('Tariff Information', {
|
37
|
+
'fields': ['name', 'display_name', 'description']
|
38
|
+
}),
|
39
|
+
('Pricing & Limits', {
|
40
|
+
'fields': ['monthly_price', 'request_limit']
|
41
|
+
}),
|
42
|
+
('Settings', {
|
43
|
+
'fields': ['is_active']
|
44
|
+
}),
|
45
|
+
('Timestamps', {
|
46
|
+
'fields': ['created_at', 'updated_at'],
|
47
|
+
'classes': ['collapse']
|
48
|
+
})
|
49
|
+
]
|
50
|
+
|
51
|
+
@display(description="Tariff")
|
52
|
+
def tariff_display(self, obj):
|
53
|
+
"""Display tariff name and description."""
|
54
|
+
return format_html(
|
55
|
+
'<strong>{}</strong><br><small>{}</small>',
|
56
|
+
obj.display_name,
|
57
|
+
obj.description[:50] + '...' if len(obj.description) > 50 else obj.description
|
58
|
+
)
|
59
|
+
|
60
|
+
@display(description="Price")
|
61
|
+
def price_display(self, obj):
|
62
|
+
"""Display price with free indicator."""
|
63
|
+
if obj.monthly_price == 0:
|
64
|
+
return format_html(
|
65
|
+
'<span style="background: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">FREE</span>'
|
66
|
+
)
|
67
|
+
else:
|
68
|
+
return format_html(
|
69
|
+
'<strong>${:.2f}</strong>/month',
|
70
|
+
obj.monthly_price
|
71
|
+
)
|
72
|
+
|
73
|
+
@display(description="Request Limit")
|
74
|
+
def limit_display(self, obj):
|
75
|
+
"""Display request limit."""
|
76
|
+
if obj.request_limit == 0:
|
77
|
+
return format_html(
|
78
|
+
'<span style="color: #28a745; font-weight: bold;">Unlimited</span>'
|
79
|
+
)
|
80
|
+
else:
|
81
|
+
return format_html(
|
82
|
+
'<strong>{:,}</strong>/month',
|
83
|
+
obj.request_limit
|
84
|
+
)
|
85
|
+
|
86
|
+
@display(description="Status")
|
87
|
+
def status_display(self, obj):
|
88
|
+
"""Display status badge."""
|
89
|
+
if obj.is_active:
|
90
|
+
return format_html(
|
91
|
+
'<span style="background: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">Active</span>'
|
92
|
+
)
|
93
|
+
else:
|
94
|
+
return format_html(
|
95
|
+
'<span style="background: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">Inactive</span>'
|
96
|
+
)
|
97
|
+
|
98
|
+
@display(description="Endpoint Groups")
|
99
|
+
def endpoint_groups_count(self, obj):
|
100
|
+
"""Display count of endpoint groups."""
|
101
|
+
count = obj.endpoint_groups.filter(is_enabled=True).count()
|
102
|
+
total = obj.endpoint_groups.count()
|
103
|
+
|
104
|
+
if total == 0:
|
105
|
+
return format_html('<span style="color: #6c757d;">No groups</span>')
|
106
|
+
|
107
|
+
return format_html(
|
108
|
+
'<strong>{}</strong> active<br><small>{} total</small>',
|
109
|
+
count,
|
110
|
+
total
|
111
|
+
)
|
112
|
+
|
113
|
+
@display(description="Created")
|
114
|
+
def created_at_display(self, obj):
|
115
|
+
"""Display creation date."""
|
116
|
+
return naturaltime(obj.created_at)
|
117
|
+
|
118
|
+
|
119
|
+
@admin.register(TariffEndpointGroup)
|
120
|
+
class TariffEndpointGroupAdmin(ModelAdmin):
|
121
|
+
"""Admin interface for tariff endpoint group associations."""
|
122
|
+
|
123
|
+
list_display = [
|
124
|
+
'association_display',
|
125
|
+
'tariff_display',
|
126
|
+
'endpoint_group_display',
|
127
|
+
'status_display',
|
128
|
+
'created_at_display'
|
129
|
+
]
|
130
|
+
|
131
|
+
list_display_links = ['association_display']
|
132
|
+
|
133
|
+
search_fields = [
|
134
|
+
'tariff__name',
|
135
|
+
'tariff__display_name',
|
136
|
+
'endpoint_group__name',
|
137
|
+
'endpoint_group__display_name'
|
138
|
+
]
|
139
|
+
|
140
|
+
list_filter = ['is_enabled', 'tariff', 'endpoint_group', 'created_at']
|
141
|
+
|
142
|
+
readonly_fields = ['created_at', 'updated_at']
|
143
|
+
|
144
|
+
fieldsets = [
|
145
|
+
('Association', {
|
146
|
+
'fields': ['tariff', 'endpoint_group', 'is_enabled']
|
147
|
+
}),
|
148
|
+
('Timestamps', {
|
149
|
+
'fields': ['created_at', 'updated_at'],
|
150
|
+
'classes': ['collapse']
|
151
|
+
})
|
152
|
+
]
|
153
|
+
|
154
|
+
@display(description="Association")
|
155
|
+
def association_display(self, obj):
|
156
|
+
"""Display association ID."""
|
157
|
+
return format_html(
|
158
|
+
'<strong>#{}</strong><br><small>{} → {}</small>',
|
159
|
+
str(obj.id)[:8],
|
160
|
+
obj.tariff.name,
|
161
|
+
obj.endpoint_group.name
|
162
|
+
)
|
163
|
+
|
164
|
+
@display(description="Tariff")
|
165
|
+
def tariff_display(self, obj):
|
166
|
+
"""Display tariff information."""
|
167
|
+
price_text = 'FREE' if obj.tariff.monthly_price == 0 else f'${obj.tariff.monthly_price:.2f}/mo'
|
168
|
+
|
169
|
+
return format_html(
|
170
|
+
'<strong>{}</strong><br><small>{}</small>',
|
171
|
+
obj.tariff.display_name,
|
172
|
+
price_text
|
173
|
+
)
|
174
|
+
|
175
|
+
@display(description="Endpoint Group")
|
176
|
+
def endpoint_group_display(self, obj):
|
177
|
+
"""Display endpoint group information."""
|
178
|
+
return format_html(
|
179
|
+
'<strong>{}</strong><br><small>{}</small>',
|
180
|
+
obj.endpoint_group.display_name,
|
181
|
+
obj.endpoint_group.description[:30] + '...' if len(obj.endpoint_group.description) > 30 else obj.endpoint_group.description
|
182
|
+
)
|
183
|
+
|
184
|
+
@display(description="Status")
|
185
|
+
def status_display(self, obj):
|
186
|
+
"""Display status badge."""
|
187
|
+
if obj.is_enabled:
|
188
|
+
return format_html(
|
189
|
+
'<span style="background: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">Enabled</span>'
|
190
|
+
)
|
191
|
+
else:
|
192
|
+
return format_html(
|
193
|
+
'<span style="background: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">Disabled</span>'
|
194
|
+
)
|
195
|
+
|
196
|
+
@display(description="Created")
|
197
|
+
def created_at_display(self, obj):
|
198
|
+
"""Display creation date."""
|
199
|
+
return naturaltime(obj.created_at)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
"""
|
2
|
+
Universal Payment Configuration.
|
3
|
+
|
4
|
+
Modular configuration system for the payments module.
|
5
|
+
"""
|
6
|
+
|
7
|
+
# Core configuration classes
|
8
|
+
from .settings import (
|
9
|
+
PaymentsSettings,
|
10
|
+
SecuritySettings,
|
11
|
+
RateLimitSettings,
|
12
|
+
BillingSettings,
|
13
|
+
CacheSettings,
|
14
|
+
NotificationSettings
|
15
|
+
)
|
16
|
+
|
17
|
+
# Provider configurations
|
18
|
+
from .providers import (
|
19
|
+
PaymentProviderConfig,
|
20
|
+
NowPaymentsConfig,
|
21
|
+
StripeConfig,
|
22
|
+
CryptAPIConfig,
|
23
|
+
get_provider_config_class,
|
24
|
+
PROVIDER_CONFIGS
|
25
|
+
)
|
26
|
+
|
27
|
+
# Configuration module
|
28
|
+
from .module import PaymentsCfgModule
|
29
|
+
|
30
|
+
# Utility functions
|
31
|
+
from .utils import (
|
32
|
+
get_payments_config,
|
33
|
+
get_provider_config,
|
34
|
+
get_nowpayments_config,
|
35
|
+
get_stripe_config,
|
36
|
+
is_payments_enabled,
|
37
|
+
is_feature_enabled,
|
38
|
+
get_enabled_providers,
|
39
|
+
get_provider_settings,
|
40
|
+
validate_provider_config,
|
41
|
+
get_rate_limit_settings,
|
42
|
+
get_cache_settings,
|
43
|
+
get_billing_settings,
|
44
|
+
reset_config_cache,
|
45
|
+
reload_config
|
46
|
+
)
|
47
|
+
|
48
|
+
# Backwards compatibility exports
|
49
|
+
payments_config = PaymentsCfgModule()
|
50
|
+
|
51
|
+
__all__ = [
|
52
|
+
# Core settings
|
53
|
+
'PaymentsSettings',
|
54
|
+
'SecuritySettings',
|
55
|
+
'RateLimitSettings',
|
56
|
+
'BillingSettings',
|
57
|
+
'CacheSettings',
|
58
|
+
'NotificationSettings',
|
59
|
+
|
60
|
+
# Provider configurations
|
61
|
+
'PaymentProviderConfig',
|
62
|
+
'NowPaymentsConfig',
|
63
|
+
'StripeConfig',
|
64
|
+
'CryptAPIConfig',
|
65
|
+
'get_provider_config_class',
|
66
|
+
'PROVIDER_CONFIGS',
|
67
|
+
|
68
|
+
# Configuration module
|
69
|
+
'PaymentsCfgModule',
|
70
|
+
'payments_config',
|
71
|
+
|
72
|
+
# Utility functions
|
73
|
+
'get_payments_config',
|
74
|
+
'get_provider_config',
|
75
|
+
'get_nowpayments_config',
|
76
|
+
'get_stripe_config',
|
77
|
+
'is_payments_enabled',
|
78
|
+
'is_feature_enabled',
|
79
|
+
'get_enabled_providers',
|
80
|
+
'get_provider_settings',
|
81
|
+
'validate_provider_config',
|
82
|
+
'get_rate_limit_settings',
|
83
|
+
'get_cache_settings',
|
84
|
+
'get_billing_settings',
|
85
|
+
'reset_config_cache',
|
86
|
+
'reload_config',
|
87
|
+
]
|
@@ -0,0 +1,162 @@
|
|
1
|
+
"""
|
2
|
+
Payment configuration module.
|
3
|
+
|
4
|
+
Handles loading and managing payment configurations from project settings.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional
|
8
|
+
from pydantic import SecretStr
|
9
|
+
import logging
|
10
|
+
|
11
|
+
from django_cfg.modules.base import BaseCfgModule
|
12
|
+
from .settings import PaymentsSettings
|
13
|
+
from .providers import NowPaymentsConfig, StripeConfig, CryptAPIConfig, get_provider_config_class
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class PaymentsCfgModule(BaseCfgModule):
|
19
|
+
"""Payment configuration module for django-cfg."""
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
super().__init__()
|
23
|
+
self.settings_class = PaymentsSettings
|
24
|
+
self._settings_cache = None
|
25
|
+
|
26
|
+
def get_config(self) -> PaymentsSettings:
|
27
|
+
"""Get payments configuration."""
|
28
|
+
if self._settings_cache is None:
|
29
|
+
project_config = super().get_config()
|
30
|
+
if project_config:
|
31
|
+
self._settings_cache = self.load_from_project_config(project_config)
|
32
|
+
else:
|
33
|
+
self._settings_cache = PaymentsSettings()
|
34
|
+
return self._settings_cache
|
35
|
+
|
36
|
+
def reset_cache(self):
|
37
|
+
"""Reset configuration cache."""
|
38
|
+
self._settings_cache = None
|
39
|
+
|
40
|
+
def load_from_project_config(self, config) -> PaymentsSettings:
|
41
|
+
"""Load payments configuration from main project config."""
|
42
|
+
# Get base settings
|
43
|
+
settings_dict = {}
|
44
|
+
|
45
|
+
# Load from django-cfg config if available
|
46
|
+
if hasattr(config, 'enable_payments'):
|
47
|
+
settings_dict['enabled'] = config.enable_payments
|
48
|
+
|
49
|
+
if hasattr(config, 'debug'):
|
50
|
+
settings_dict['debug_mode'] = config.debug
|
51
|
+
|
52
|
+
# Load provider configurations from environment/config
|
53
|
+
providers = {}
|
54
|
+
|
55
|
+
# Load NowPayments configuration
|
56
|
+
if self._has_provider_config(config, 'nowpayments'):
|
57
|
+
nowpayments_config = self._get_provider_config(config, 'nowpayments')
|
58
|
+
if nowpayments_config:
|
59
|
+
providers['nowpayments'] = nowpayments_config
|
60
|
+
|
61
|
+
# Load Stripe configuration
|
62
|
+
if self._has_provider_config(config, 'stripe'):
|
63
|
+
stripe_config = self._get_provider_config(config, 'stripe')
|
64
|
+
if stripe_config:
|
65
|
+
providers['stripe'] = stripe_config
|
66
|
+
|
67
|
+
# Load CryptAPI configuration
|
68
|
+
if self._has_provider_config(config, 'cryptapi'):
|
69
|
+
cryptapi_config = self._get_provider_config(config, 'cryptapi')
|
70
|
+
if cryptapi_config:
|
71
|
+
providers['cryptapi'] = cryptapi_config
|
72
|
+
|
73
|
+
|
74
|
+
settings_dict['providers'] = providers
|
75
|
+
|
76
|
+
return PaymentsSettings(**settings_dict)
|
77
|
+
|
78
|
+
def _has_provider_config(self, config, provider_name: str) -> bool:
|
79
|
+
"""Check if provider configuration exists."""
|
80
|
+
return (
|
81
|
+
hasattr(config, 'api_keys') and
|
82
|
+
hasattr(config.api_keys, provider_name)
|
83
|
+
)
|
84
|
+
|
85
|
+
def _get_provider_config(self, config, provider_name: str) -> Optional[object]:
|
86
|
+
"""Get provider configuration."""
|
87
|
+
try:
|
88
|
+
if provider_name == 'nowpayments':
|
89
|
+
return self._load_nowpayments_config(config)
|
90
|
+
elif provider_name == 'stripe':
|
91
|
+
return self._load_stripe_config(config)
|
92
|
+
elif provider_name == 'cryptapi':
|
93
|
+
return self._load_cryptapi_config(config)
|
94
|
+
else:
|
95
|
+
logger.warning(f"Unknown provider: {provider_name}")
|
96
|
+
return None
|
97
|
+
except Exception as e:
|
98
|
+
logger.error(f"Error loading {provider_name} config: {e}")
|
99
|
+
return None
|
100
|
+
|
101
|
+
def _load_nowpayments_config(self, config) -> Optional[NowPaymentsConfig]:
|
102
|
+
"""Load NowPayments configuration."""
|
103
|
+
nowpayments_config = config.api_keys.nowpayments
|
104
|
+
if not hasattr(nowpayments_config, 'api_key'):
|
105
|
+
return None
|
106
|
+
|
107
|
+
return NowPaymentsConfig(
|
108
|
+
api_key=SecretStr(nowpayments_config.api_key),
|
109
|
+
public_key=SecretStr(nowpayments_config.public_key) if hasattr(nowpayments_config, 'public_key') else None,
|
110
|
+
sandbox=getattr(config, 'debug', True),
|
111
|
+
callback_url=self._build_callback_url(config, 'nowpayments'),
|
112
|
+
success_url=self._build_success_url(config),
|
113
|
+
cancel_url=self._build_cancel_url(config)
|
114
|
+
)
|
115
|
+
|
116
|
+
def _load_stripe_config(self, config) -> Optional[StripeConfig]:
|
117
|
+
"""Load Stripe configuration."""
|
118
|
+
stripe_config = config.api_keys.stripe
|
119
|
+
if not hasattr(stripe_config, 'secret_key'):
|
120
|
+
return None
|
121
|
+
|
122
|
+
return StripeConfig(
|
123
|
+
api_key=SecretStr(stripe_config.secret_key),
|
124
|
+
publishable_key=SecretStr(stripe_config.publishable_key) if hasattr(stripe_config, 'publishable_key') else None,
|
125
|
+
webhook_secret=SecretStr(stripe_config.webhook_secret) if hasattr(stripe_config, 'webhook_secret') else None,
|
126
|
+
sandbox=getattr(config, 'debug', True)
|
127
|
+
)
|
128
|
+
|
129
|
+
def _load_cryptapi_config(self, config) -> Optional[CryptAPIConfig]:
|
130
|
+
"""Load CryptAPI configuration."""
|
131
|
+
cryptapi_config = config.api_keys.cryptapi
|
132
|
+
if not hasattr(cryptapi_config, 'own_address'):
|
133
|
+
return None
|
134
|
+
|
135
|
+
return CryptAPIConfig(
|
136
|
+
api_key=SecretStr('dummy'), # CryptAPI doesn't require API key
|
137
|
+
own_address=cryptapi_config.own_address,
|
138
|
+
callback_url=self._build_callback_url(config, 'cryptapi'),
|
139
|
+
convert_payments=getattr(cryptapi_config, 'convert_payments', True),
|
140
|
+
multi_token=getattr(cryptapi_config, 'multi_token', True),
|
141
|
+
priority=getattr(cryptapi_config, 'priority', 'default'),
|
142
|
+
sandbox=getattr(config, 'debug', True)
|
143
|
+
)
|
144
|
+
|
145
|
+
|
146
|
+
def _build_callback_url(self, config, provider: str) -> Optional[str]:
|
147
|
+
"""Build webhook callback URL for provider."""
|
148
|
+
if hasattr(config, 'api_url') and config.api_url:
|
149
|
+
return f"{config.api_url}/api/payments/webhook/{provider}/"
|
150
|
+
return None
|
151
|
+
|
152
|
+
def _build_success_url(self, config) -> Optional[str]:
|
153
|
+
"""Build payment success URL."""
|
154
|
+
if hasattr(config, 'site_url') and config.site_url:
|
155
|
+
return f"{config.site_url}/payments/success/"
|
156
|
+
return None
|
157
|
+
|
158
|
+
def _build_cancel_url(self, config) -> Optional[str]:
|
159
|
+
"""Build payment cancel URL."""
|
160
|
+
if hasattr(config, 'site_url') and config.site_url:
|
161
|
+
return f"{config.site_url}/payments/cancel/"
|
162
|
+
return None
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""
|
2
|
+
Payment provider configurations.
|
3
|
+
|
4
|
+
Defines configuration classes for different payment providers.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, Optional
|
8
|
+
from pydantic import BaseModel, Field, SecretStr
|
9
|
+
|
10
|
+
|
11
|
+
class PaymentProviderConfig(BaseModel):
|
12
|
+
"""Base configuration for payment providers."""
|
13
|
+
enabled: bool = True
|
14
|
+
sandbox: bool = Field(default=True, description="Use sandbox mode")
|
15
|
+
api_key: SecretStr = Field(description="Provider API key")
|
16
|
+
timeout: int = Field(default=30, description="Request timeout in seconds")
|
17
|
+
max_retries: int = Field(default=3, description="Maximum retry attempts")
|
18
|
+
|
19
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
20
|
+
"""Get configuration as dictionary for provider initialization."""
|
21
|
+
return {
|
22
|
+
'api_key': self.api_key.get_secret_value(),
|
23
|
+
'sandbox': self.sandbox,
|
24
|
+
'timeout': self.timeout,
|
25
|
+
'max_retries': self.max_retries
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
class NowPaymentsConfig(PaymentProviderConfig):
|
30
|
+
"""NowPayments provider configuration."""
|
31
|
+
public_key: Optional[SecretStr] = Field(default=None, description="NowPayments public key")
|
32
|
+
callback_url: Optional[str] = Field(default=None, description="Webhook callback URL")
|
33
|
+
success_url: Optional[str] = Field(default=None, description="Payment success URL")
|
34
|
+
cancel_url: Optional[str] = Field(default=None, description="Payment cancel URL")
|
35
|
+
|
36
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
37
|
+
"""Get configuration as dictionary for provider initialization."""
|
38
|
+
config = super().get_config_dict()
|
39
|
+
config.update({
|
40
|
+
'callback_url': self.callback_url,
|
41
|
+
'success_url': self.success_url,
|
42
|
+
'cancel_url': self.cancel_url,
|
43
|
+
})
|
44
|
+
return config
|
45
|
+
|
46
|
+
|
47
|
+
class StripeConfig(PaymentProviderConfig):
|
48
|
+
"""Stripe provider configuration."""
|
49
|
+
publishable_key: Optional[SecretStr] = Field(default=None, description="Stripe publishable key")
|
50
|
+
webhook_secret: Optional[SecretStr] = Field(default=None, description="Stripe webhook secret")
|
51
|
+
|
52
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
53
|
+
"""Get configuration as dictionary for provider initialization."""
|
54
|
+
config = super().get_config_dict()
|
55
|
+
config.update({
|
56
|
+
'publishable_key': self.publishable_key.get_secret_value() if self.publishable_key else None,
|
57
|
+
'webhook_secret': self.webhook_secret.get_secret_value() if self.webhook_secret else None,
|
58
|
+
})
|
59
|
+
return config
|
60
|
+
|
61
|
+
|
62
|
+
class CryptAPIConfig(PaymentProviderConfig):
|
63
|
+
"""CryptAPI provider configuration."""
|
64
|
+
own_address: str = Field(description="Your crypto address where funds will be sent")
|
65
|
+
callback_url: Optional[str] = Field(default=None, description="Webhook callback URL")
|
66
|
+
convert_payments: bool = Field(default=True, description="Auto-convert payments to your address currency")
|
67
|
+
multi_token: bool = Field(default=True, description="Enable multi-token support")
|
68
|
+
priority: str = Field(default='default', description="Transaction priority (default, economic, priority)")
|
69
|
+
|
70
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
71
|
+
"""Get configuration as dictionary for provider initialization."""
|
72
|
+
config = super().get_config_dict()
|
73
|
+
config.update({
|
74
|
+
'own_address': self.own_address,
|
75
|
+
'callback_url': self.callback_url,
|
76
|
+
'convert_payments': self.convert_payments,
|
77
|
+
'multi_token': self.multi_token,
|
78
|
+
'priority': self.priority,
|
79
|
+
})
|
80
|
+
return config
|
81
|
+
|
82
|
+
|
83
|
+
# Provider registry for easy access
|
84
|
+
PROVIDER_CONFIGS = {
|
85
|
+
'nowpayments': NowPaymentsConfig,
|
86
|
+
'stripe': StripeConfig,
|
87
|
+
'cryptapi': CryptAPIConfig,
|
88
|
+
}
|
89
|
+
|
90
|
+
|
91
|
+
def get_provider_config_class(provider_name: str) -> Optional[type]:
|
92
|
+
"""Get provider configuration class by name."""
|
93
|
+
return PROVIDER_CONFIGS.get(provider_name)
|
@@ -0,0 +1,136 @@
|
|
1
|
+
"""
|
2
|
+
Universal payments module settings.
|
3
|
+
|
4
|
+
Core settings for the payments system including security, rate limiting, billing, etc.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict
|
8
|
+
from pydantic import BaseModel, Field
|
9
|
+
|
10
|
+
from .providers import PaymentProviderConfig
|
11
|
+
|
12
|
+
|
13
|
+
class SecuritySettings(BaseModel):
|
14
|
+
"""Security-related payment settings."""
|
15
|
+
api_key_length: int = Field(default=32, description="API key length in characters")
|
16
|
+
api_key_prefix: str = Field(default="ak_", description="API key prefix")
|
17
|
+
min_balance_threshold: float = Field(default=0.0, description="Minimum balance threshold")
|
18
|
+
auto_create_api_keys: bool = Field(default=True, description="Auto-create API keys for new users")
|
19
|
+
|
20
|
+
|
21
|
+
class RateLimitSettings(BaseModel):
|
22
|
+
"""Rate limiting settings."""
|
23
|
+
default_rate_limit_per_hour: int = Field(default=1000, description="Default API rate limit per hour")
|
24
|
+
default_rate_limit_per_day: int = Field(default=10000, description="Default API rate limit per day")
|
25
|
+
burst_limit_multiplier: float = Field(default=2.0, description="Burst limit multiplier")
|
26
|
+
sliding_window_size: int = Field(default=3600, description="Sliding window size in seconds")
|
27
|
+
|
28
|
+
|
29
|
+
class BillingSettings(BaseModel):
|
30
|
+
"""Billing and subscription settings."""
|
31
|
+
auto_bill_subscriptions: bool = Field(default=True, description="Automatically bill subscriptions")
|
32
|
+
billing_grace_period_hours: int = Field(default=24, description="Grace period for failed billing")
|
33
|
+
retry_failed_payments: bool = Field(default=True, description="Retry failed payments")
|
34
|
+
max_payment_retries: int = Field(default=3, description="Maximum payment retry attempts")
|
35
|
+
min_payment_amount_usd: float = Field(default=1.0, description="Minimum payment amount in USD")
|
36
|
+
max_payment_amount_usd: float = Field(default=50000.0, description="Maximum payment amount in USD")
|
37
|
+
|
38
|
+
|
39
|
+
class CacheSettings(BaseModel):
|
40
|
+
"""Cache timeout settings."""
|
41
|
+
cache_timeout_access_check: int = Field(default=60, description="Cache timeout for access checks (seconds)")
|
42
|
+
cache_timeout_user_balance: int = Field(default=300, description="Cache timeout for user balance (seconds)")
|
43
|
+
cache_timeout_subscriptions: int = Field(default=600, description="Cache timeout for subscriptions (seconds)")
|
44
|
+
cache_timeout_provider_status: int = Field(default=1800, description="Cache timeout for provider status (seconds)")
|
45
|
+
cache_timeout_currency_rates: int = Field(default=3600, description="Cache timeout for currency rates (seconds)")
|
46
|
+
|
47
|
+
|
48
|
+
class NotificationSettings(BaseModel):
|
49
|
+
"""Notification settings."""
|
50
|
+
send_payment_confirmations: bool = Field(default=True, description="Send payment confirmation emails")
|
51
|
+
send_subscription_renewals: bool = Field(default=True, description="Send subscription renewal notifications")
|
52
|
+
send_balance_alerts: bool = Field(default=True, description="Send low balance alerts")
|
53
|
+
send_api_key_alerts: bool = Field(default=True, description="Send API key security alerts")
|
54
|
+
webhook_timeout: int = Field(default=30, description="Webhook timeout in seconds")
|
55
|
+
|
56
|
+
|
57
|
+
class PaymentsSettings(BaseModel):
|
58
|
+
"""Universal payments module settings."""
|
59
|
+
|
60
|
+
# General settings
|
61
|
+
enabled: bool = Field(default=True, description="Enable payments module")
|
62
|
+
debug_mode: bool = Field(default=False, description="Enable debug mode for payments")
|
63
|
+
|
64
|
+
# Component settings
|
65
|
+
security: SecuritySettings = Field(default_factory=SecuritySettings)
|
66
|
+
rate_limits: RateLimitSettings = Field(default_factory=RateLimitSettings)
|
67
|
+
billing: BillingSettings = Field(default_factory=BillingSettings)
|
68
|
+
cache: CacheSettings = Field(default_factory=CacheSettings)
|
69
|
+
notifications: NotificationSettings = Field(default_factory=NotificationSettings)
|
70
|
+
|
71
|
+
# Provider configurations
|
72
|
+
providers: Dict[str, PaymentProviderConfig] = Field(default_factory=dict, description="Payment provider configurations")
|
73
|
+
|
74
|
+
# Feature flags
|
75
|
+
enable_crypto_payments: bool = Field(default=True, description="Enable cryptocurrency payments")
|
76
|
+
enable_fiat_payments: bool = Field(default=True, description="Enable fiat currency payments")
|
77
|
+
enable_subscription_system: bool = Field(default=True, description="Enable subscription system")
|
78
|
+
enable_balance_system: bool = Field(default=True, description="Enable user balance system")
|
79
|
+
enable_api_key_system: bool = Field(default=True, description="Enable API key system")
|
80
|
+
enable_webhook_processing: bool = Field(default=True, description="Enable webhook processing")
|
81
|
+
|
82
|
+
# Backwards compatibility properties
|
83
|
+
@property
|
84
|
+
def auto_create_api_keys(self) -> bool:
|
85
|
+
"""Backwards compatibility for auto_create_api_keys."""
|
86
|
+
return self.security.auto_create_api_keys
|
87
|
+
|
88
|
+
@property
|
89
|
+
def min_balance_threshold(self) -> float:
|
90
|
+
"""Backwards compatibility for min_balance_threshold."""
|
91
|
+
return self.security.min_balance_threshold
|
92
|
+
|
93
|
+
@property
|
94
|
+
def default_rate_limit_per_hour(self) -> int:
|
95
|
+
"""Backwards compatibility for default_rate_limit_per_hour."""
|
96
|
+
return self.rate_limits.default_rate_limit_per_hour
|
97
|
+
|
98
|
+
@property
|
99
|
+
def default_rate_limit_per_day(self) -> int:
|
100
|
+
"""Backwards compatibility for default_rate_limit_per_day."""
|
101
|
+
return self.rate_limits.default_rate_limit_per_day
|
102
|
+
|
103
|
+
@property
|
104
|
+
def api_key_length(self) -> int:
|
105
|
+
"""Backwards compatibility for api_key_length."""
|
106
|
+
return self.security.api_key_length
|
107
|
+
|
108
|
+
@property
|
109
|
+
def api_key_prefix(self) -> str:
|
110
|
+
"""Backwards compatibility for api_key_prefix."""
|
111
|
+
return self.security.api_key_prefix
|
112
|
+
|
113
|
+
@property
|
114
|
+
def auto_bill_subscriptions(self) -> bool:
|
115
|
+
"""Backwards compatibility for auto_bill_subscriptions."""
|
116
|
+
return self.billing.auto_bill_subscriptions
|
117
|
+
|
118
|
+
@property
|
119
|
+
def billing_grace_period_hours(self) -> int:
|
120
|
+
"""Backwards compatibility for billing_grace_period_hours."""
|
121
|
+
return self.billing.billing_grace_period_hours
|
122
|
+
|
123
|
+
@property
|
124
|
+
def cache_timeout_access_check(self) -> int:
|
125
|
+
"""Backwards compatibility for cache_timeout_access_check."""
|
126
|
+
return self.cache.cache_timeout_access_check
|
127
|
+
|
128
|
+
@property
|
129
|
+
def cache_timeout_user_balance(self) -> int:
|
130
|
+
"""Backwards compatibility for cache_timeout_user_balance."""
|
131
|
+
return self.cache.cache_timeout_user_balance
|
132
|
+
|
133
|
+
@property
|
134
|
+
def cache_timeout_subscriptions(self) -> int:
|
135
|
+
"""Backwards compatibility for cache_timeout_subscriptions."""
|
136
|
+
return self.cache.cache_timeout_subscriptions
|