django-cfg 1.2.22__py3-none-any.whl → 1.2.25__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/knowbase/tasks/archive_tasks.py +6 -6
- django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
- django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
- 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 +65 -0
- django_cfg/apps/payments/config/module.py +70 -0
- django_cfg/apps/payments/config/providers.py +115 -0
- django_cfg/apps/payments/config/settings.py +96 -0
- django_cfg/apps/payments/config/utils.py +52 -0
- django_cfg/apps/payments/decorators.py +291 -0
- django_cfg/apps/payments/management/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/README.md +178 -0
- django_cfg/apps/payments/management/commands/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
- django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
- django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
- django_cfg/apps/payments/managers/currency_manager.py +65 -14
- django_cfg/apps/payments/middleware/api_access.py +294 -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 +125 -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 +112 -2
- django_cfg/apps/payments/models/subscriptions.py +2 -2
- django_cfg/apps/payments/services/__init__.py +64 -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 +447 -0
- django_cfg/apps/payments/services/core/fallback_service.py +432 -0
- django_cfg/apps/payments/services/core/payment_service.py +576 -0
- django_cfg/apps/payments/services/core/subscription_service.py +614 -0
- django_cfg/apps/payments/services/internal_types.py +297 -0
- django_cfg/apps/payments/services/middleware/__init__.py +8 -0
- django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
- django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
- django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
- django_cfg/apps/payments/services/providers/__init__.py +22 -0
- django_cfg/apps/payments/services/providers/base.py +137 -0
- django_cfg/apps/payments/services/providers/cryptapi.py +273 -0
- django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
- django_cfg/apps/payments/services/providers/nowpayments.py +293 -0
- django_cfg/apps/payments/services/providers/registry.py +103 -0
- django_cfg/apps/payments/services/security/__init__.py +34 -0
- django_cfg/apps/payments/services/security/error_handler.py +637 -0
- django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
- django_cfg/apps/payments/services/security/webhook_validator.py +475 -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 +160 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -0
- django_cfg/apps/payments/signals/subscription_signals.py +196 -0
- django_cfg/apps/payments/tasks/__init__.py +12 -0
- django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
- django_cfg/apps/payments/urls.py +5 -5
- django_cfg/apps/payments/utils/__init__.py +45 -0
- django_cfg/apps/payments/utils/billing_utils.py +342 -0
- django_cfg/apps/payments/utils/config_utils.py +245 -0
- django_cfg/apps/payments/utils/middleware_utils.py +228 -0
- django_cfg/apps/payments/utils/validation_utils.py +94 -0
- django_cfg/apps/payments/views/payment_views.py +40 -2
- django_cfg/apps/payments/views/webhook_views.py +266 -0
- django_cfg/apps/payments/viewsets.py +65 -0
- django_cfg/apps/support/signals.py +16 -4
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/cli/README.md +2 -2
- django_cfg/cli/commands/create_project.py +1 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/main.py +1 -1
- django_cfg/cli/utils.py +5 -5
- django_cfg/core/config.py +18 -4
- django_cfg/models/payments.py +546 -0
- django_cfg/models/revolution.py +1 -1
- django_cfg/models/tasks.py +51 -2
- django_cfg/modules/base.py +12 -6
- django_cfg/modules/django_currency/README.md +104 -269
- django_cfg/modules/django_currency/__init__.py +99 -41
- django_cfg/modules/django_currency/clients/__init__.py +11 -0
- django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
- django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
- django_cfg/modules/django_currency/core/__init__.py +42 -0
- django_cfg/modules/django_currency/core/converter.py +169 -0
- django_cfg/modules/django_currency/core/exceptions.py +28 -0
- django_cfg/modules/django_currency/core/models.py +54 -0
- django_cfg/modules/django_currency/database/__init__.py +25 -0
- django_cfg/modules/django_currency/database/database_loader.py +507 -0
- django_cfg/modules/django_currency/utils/__init__.py +9 -0
- django_cfg/modules/django_currency/utils/cache.py +92 -0
- django_cfg/modules/django_email.py +42 -4
- django_cfg/modules/django_unfold/dashboard.py +20 -0
- django_cfg/registry/core.py +10 -0
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/METADATA +11 -6
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/RECORD +113 -50
- django_cfg/apps/agents/examples/__init__.py +0 -3
- django_cfg/apps/agents/examples/simple_example.py +0 -161
- django_cfg/apps/knowbase/examples/__init__.py +0 -3
- django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
- 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/modules/django_currency/cache.py +0 -430
- django_cfg/modules/django_currency/converter.py +0 -324
- django_cfg/modules/django_currency/service.py +0 -277
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.22.dist-info → django_cfg-1.2.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,546 @@
|
|
1
|
+
"""
|
2
|
+
Payment system configuration models for Django-CFG.
|
3
|
+
|
4
|
+
This module provides type-safe Pydantic models for configuring the universal
|
5
|
+
payment system, including provider configurations, security settings,
|
6
|
+
and integration options.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from pydantic import BaseModel, Field, SecretStr, field_validator
|
10
|
+
from typing import Optional, List, Dict, Any, Literal
|
11
|
+
from enum import Enum
|
12
|
+
from decimal import Decimal
|
13
|
+
import logging
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class PaymentProvider(str, Enum):
|
19
|
+
"""Supported payment providers."""
|
20
|
+
NOWPAYMENTS = "nowpayments"
|
21
|
+
CRYPTAPI = "cryptapi"
|
22
|
+
STRIPE = "stripe"
|
23
|
+
# Future providers can be added here
|
24
|
+
|
25
|
+
|
26
|
+
class BillingPeriod(str, Enum):
|
27
|
+
"""Supported billing periods."""
|
28
|
+
MONTHLY = "monthly"
|
29
|
+
YEARLY = "yearly"
|
30
|
+
|
31
|
+
|
32
|
+
class PaymentProviderConfig(BaseModel):
|
33
|
+
"""Base configuration for payment providers."""
|
34
|
+
|
35
|
+
name: str = Field(
|
36
|
+
description="Provider name (e.g., 'nowpayments', 'cryptapi', 'stripe')"
|
37
|
+
)
|
38
|
+
enabled: bool = Field(
|
39
|
+
default=True,
|
40
|
+
description="Enable this payment provider"
|
41
|
+
)
|
42
|
+
sandbox: bool = Field(
|
43
|
+
default=True,
|
44
|
+
description="Use sandbox/test mode"
|
45
|
+
)
|
46
|
+
api_key: SecretStr = Field(
|
47
|
+
description="Provider API key (stored securely)"
|
48
|
+
)
|
49
|
+
timeout: int = Field(
|
50
|
+
default=30,
|
51
|
+
ge=5,
|
52
|
+
le=300,
|
53
|
+
description="Request timeout in seconds"
|
54
|
+
)
|
55
|
+
max_retries: int = Field(
|
56
|
+
default=3,
|
57
|
+
ge=0,
|
58
|
+
le=10,
|
59
|
+
description="Maximum retry attempts for failed requests"
|
60
|
+
)
|
61
|
+
|
62
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
63
|
+
"""Get configuration as dictionary for provider initialization."""
|
64
|
+
return {
|
65
|
+
'enabled': self.enabled,
|
66
|
+
'api_key': self.api_key.get_secret_value(),
|
67
|
+
'sandbox': self.sandbox,
|
68
|
+
'timeout': self.timeout,
|
69
|
+
'max_retries': self.max_retries
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
class NowPaymentsConfig(PaymentProviderConfig):
|
74
|
+
"""NowPayments cryptocurrency provider configuration."""
|
75
|
+
|
76
|
+
ipn_secret: Optional[SecretStr] = Field(
|
77
|
+
default=None,
|
78
|
+
description="IPN secret for webhook validation"
|
79
|
+
)
|
80
|
+
callback_url: Optional[str] = Field(
|
81
|
+
default=None,
|
82
|
+
description="Custom webhook callback URL"
|
83
|
+
)
|
84
|
+
success_url: Optional[str] = Field(
|
85
|
+
default=None,
|
86
|
+
description="Payment success redirect URL"
|
87
|
+
)
|
88
|
+
cancel_url: Optional[str] = Field(
|
89
|
+
default=None,
|
90
|
+
description="Payment cancellation redirect URL"
|
91
|
+
)
|
92
|
+
|
93
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
94
|
+
"""Get configuration with NowPayments-specific fields."""
|
95
|
+
config = super().get_config_dict()
|
96
|
+
config.update({
|
97
|
+
'ipn_secret': self.ipn_secret.get_secret_value() if self.ipn_secret else None,
|
98
|
+
'callback_url': self.callback_url,
|
99
|
+
'success_url': self.success_url,
|
100
|
+
'cancel_url': self.cancel_url
|
101
|
+
})
|
102
|
+
return config
|
103
|
+
|
104
|
+
|
105
|
+
class CryptAPIConfig(PaymentProviderConfig):
|
106
|
+
"""CryptAPI cryptocurrency provider configuration."""
|
107
|
+
|
108
|
+
own_address: str = Field(
|
109
|
+
description="Your cryptocurrency wallet address"
|
110
|
+
)
|
111
|
+
callback_url: Optional[str] = Field(
|
112
|
+
default=None,
|
113
|
+
description="Webhook callback URL"
|
114
|
+
)
|
115
|
+
convert_payments: bool = Field(
|
116
|
+
default=False,
|
117
|
+
description="Convert payments to your currency"
|
118
|
+
)
|
119
|
+
multi_token: bool = Field(
|
120
|
+
default=False,
|
121
|
+
description="Enable multi-token support"
|
122
|
+
)
|
123
|
+
priority: str = Field(
|
124
|
+
default="default",
|
125
|
+
description="Transaction priority level"
|
126
|
+
)
|
127
|
+
|
128
|
+
# CryptAPI doesn't use traditional API keys
|
129
|
+
api_key: SecretStr = Field(
|
130
|
+
default=SecretStr("not_required"),
|
131
|
+
description="Not required for CryptAPI"
|
132
|
+
)
|
133
|
+
|
134
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
135
|
+
"""Get configuration with CryptAPI-specific fields."""
|
136
|
+
config = super().get_config_dict()
|
137
|
+
config.update({
|
138
|
+
'own_address': self.own_address,
|
139
|
+
'callback_url': self.callback_url,
|
140
|
+
'convert_payments': self.convert_payments,
|
141
|
+
'multi_token': self.multi_token,
|
142
|
+
'priority': self.priority
|
143
|
+
})
|
144
|
+
return config
|
145
|
+
|
146
|
+
|
147
|
+
class StripeConfig(PaymentProviderConfig):
|
148
|
+
"""Stripe payment provider configuration."""
|
149
|
+
|
150
|
+
publishable_key: Optional[str] = Field(
|
151
|
+
default=None,
|
152
|
+
description="Stripe publishable key for frontend"
|
153
|
+
)
|
154
|
+
webhook_endpoint_secret: Optional[SecretStr] = Field(
|
155
|
+
default=None,
|
156
|
+
description="Webhook endpoint secret for signature validation"
|
157
|
+
)
|
158
|
+
success_url: Optional[str] = Field(
|
159
|
+
default=None,
|
160
|
+
description="Payment success redirect URL"
|
161
|
+
)
|
162
|
+
cancel_url: Optional[str] = Field(
|
163
|
+
default=None,
|
164
|
+
description="Payment cancellation redirect URL"
|
165
|
+
)
|
166
|
+
|
167
|
+
def get_config_dict(self) -> Dict[str, Any]:
|
168
|
+
"""Get configuration with Stripe-specific fields."""
|
169
|
+
config = super().get_config_dict()
|
170
|
+
config.update({
|
171
|
+
'publishable_key': self.publishable_key,
|
172
|
+
'webhook_endpoint_secret': self.webhook_endpoint_secret.get_secret_value() if self.webhook_endpoint_secret else None,
|
173
|
+
'success_url': self.success_url,
|
174
|
+
'cancel_url': self.cancel_url
|
175
|
+
})
|
176
|
+
return config
|
177
|
+
|
178
|
+
|
179
|
+
class SecuritySettings(BaseModel):
|
180
|
+
"""Security configuration for payments."""
|
181
|
+
|
182
|
+
auto_create_api_keys: bool = Field(
|
183
|
+
default=True,
|
184
|
+
description="Automatically create API keys for new users"
|
185
|
+
)
|
186
|
+
require_api_key: bool = Field(
|
187
|
+
default=True,
|
188
|
+
description="Require API key for payment endpoints"
|
189
|
+
)
|
190
|
+
min_balance_threshold: Decimal = Field(
|
191
|
+
default=Decimal('0.01'),
|
192
|
+
ge=0,
|
193
|
+
description="Minimum balance threshold for operations"
|
194
|
+
)
|
195
|
+
max_payment_amount: Decimal = Field(
|
196
|
+
default=Decimal('50000.00'),
|
197
|
+
gt=0,
|
198
|
+
description="Maximum payment amount in USD"
|
199
|
+
)
|
200
|
+
webhook_signature_validation: bool = Field(
|
201
|
+
default=True,
|
202
|
+
description="Validate webhook signatures"
|
203
|
+
)
|
204
|
+
|
205
|
+
|
206
|
+
class RateLimitSettings(BaseModel):
|
207
|
+
"""Rate limiting configuration."""
|
208
|
+
|
209
|
+
enabled: bool = Field(
|
210
|
+
default=True,
|
211
|
+
description="Enable rate limiting"
|
212
|
+
)
|
213
|
+
requests_per_hour: int = Field(
|
214
|
+
default=1000,
|
215
|
+
ge=1,
|
216
|
+
description="Maximum requests per hour per user"
|
217
|
+
)
|
218
|
+
payment_requests_per_hour: int = Field(
|
219
|
+
default=100,
|
220
|
+
ge=1,
|
221
|
+
description="Maximum payment requests per hour per user"
|
222
|
+
)
|
223
|
+
webhook_requests_per_minute: int = Field(
|
224
|
+
default=60,
|
225
|
+
ge=1,
|
226
|
+
description="Maximum webhook requests per minute"
|
227
|
+
)
|
228
|
+
|
229
|
+
|
230
|
+
class NotificationSettings(BaseModel):
|
231
|
+
"""Notification configuration."""
|
232
|
+
|
233
|
+
email_notifications: bool = Field(
|
234
|
+
default=True,
|
235
|
+
description="Send email notifications for payment events"
|
236
|
+
)
|
237
|
+
webhook_notifications: bool = Field(
|
238
|
+
default=True,
|
239
|
+
description="Send webhook notifications"
|
240
|
+
)
|
241
|
+
webhook_timeout: int = Field(
|
242
|
+
default=30,
|
243
|
+
ge=5,
|
244
|
+
le=300,
|
245
|
+
description="Webhook timeout in seconds"
|
246
|
+
)
|
247
|
+
|
248
|
+
|
249
|
+
class SubscriptionSettings(BaseModel):
|
250
|
+
"""Subscription system configuration."""
|
251
|
+
|
252
|
+
enabled: bool = Field(
|
253
|
+
default=True,
|
254
|
+
description="Enable subscription system"
|
255
|
+
)
|
256
|
+
auto_renewal: bool = Field(
|
257
|
+
default=True,
|
258
|
+
description="Enable automatic subscription renewal"
|
259
|
+
)
|
260
|
+
grace_period_days: int = Field(
|
261
|
+
default=3,
|
262
|
+
ge=0,
|
263
|
+
le=30,
|
264
|
+
description="Grace period for expired subscriptions"
|
265
|
+
)
|
266
|
+
trial_period_days: int = Field(
|
267
|
+
default=7,
|
268
|
+
ge=0,
|
269
|
+
le=90,
|
270
|
+
description="Default trial period in days"
|
271
|
+
)
|
272
|
+
refund_policy: Literal["none", "prorated", "full"] = Field(
|
273
|
+
default="prorated",
|
274
|
+
description="Default refund policy"
|
275
|
+
)
|
276
|
+
|
277
|
+
|
278
|
+
class PaymentsConfig(BaseModel):
|
279
|
+
"""
|
280
|
+
Universal payment system configuration.
|
281
|
+
|
282
|
+
This model provides comprehensive configuration for the django-cfg payments
|
283
|
+
module, including provider settings, security options, rate limiting,
|
284
|
+
and feature toggles.
|
285
|
+
"""
|
286
|
+
|
287
|
+
# === Core Settings ===
|
288
|
+
enabled: bool = Field(
|
289
|
+
default=True,
|
290
|
+
description="Enable the payments module"
|
291
|
+
)
|
292
|
+
debug_mode: bool = Field(
|
293
|
+
default=False,
|
294
|
+
description="Enable debug mode for detailed logging"
|
295
|
+
)
|
296
|
+
strict_mode: bool = Field(
|
297
|
+
default=True,
|
298
|
+
description="Enable strict validation and security checks"
|
299
|
+
)
|
300
|
+
|
301
|
+
|
302
|
+
# === Payment Providers ===
|
303
|
+
providers: List[PaymentProviderConfig] = Field(
|
304
|
+
default_factory=list,
|
305
|
+
description="Payment provider configurations"
|
306
|
+
)
|
307
|
+
|
308
|
+
# === Feature Configuration ===
|
309
|
+
security: SecuritySettings = Field(
|
310
|
+
default_factory=SecuritySettings,
|
311
|
+
description="Security settings"
|
312
|
+
)
|
313
|
+
rate_limits: RateLimitSettings = Field(
|
314
|
+
default_factory=RateLimitSettings,
|
315
|
+
description="Rate limiting settings"
|
316
|
+
)
|
317
|
+
notifications: NotificationSettings = Field(
|
318
|
+
default_factory=NotificationSettings,
|
319
|
+
description="Notification settings"
|
320
|
+
)
|
321
|
+
subscriptions: SubscriptionSettings = Field(
|
322
|
+
default_factory=SubscriptionSettings,
|
323
|
+
description="Subscription system settings"
|
324
|
+
)
|
325
|
+
|
326
|
+
# === Feature Flags ===
|
327
|
+
enable_crypto_payments: bool = Field(
|
328
|
+
default=True,
|
329
|
+
description="Enable cryptocurrency payments"
|
330
|
+
)
|
331
|
+
enable_fiat_payments: bool = Field(
|
332
|
+
default=True,
|
333
|
+
description="Enable fiat currency payments"
|
334
|
+
)
|
335
|
+
enable_subscription_system: bool = Field(
|
336
|
+
default=True,
|
337
|
+
description="Enable subscription management"
|
338
|
+
)
|
339
|
+
enable_balance_system: bool = Field(
|
340
|
+
default=True,
|
341
|
+
description="Enable user balance system"
|
342
|
+
)
|
343
|
+
enable_api_key_system: bool = Field(
|
344
|
+
default=True,
|
345
|
+
description="Enable API key management"
|
346
|
+
)
|
347
|
+
enable_webhook_processing: bool = Field(
|
348
|
+
default=True,
|
349
|
+
description="Enable webhook processing"
|
350
|
+
)
|
351
|
+
enable_billing_utils: bool = Field(
|
352
|
+
default=True,
|
353
|
+
description="Enable billing utilities and calculations"
|
354
|
+
)
|
355
|
+
|
356
|
+
# === Middleware Configuration ===
|
357
|
+
middleware_enabled: bool = Field(
|
358
|
+
default=True,
|
359
|
+
description="Enable payments middleware"
|
360
|
+
)
|
361
|
+
custom_middleware: List[str] = Field(
|
362
|
+
default_factory=list,
|
363
|
+
description="Additional custom middleware classes"
|
364
|
+
)
|
365
|
+
|
366
|
+
# === URL Configuration ===
|
367
|
+
url_prefix: str = Field(
|
368
|
+
default="api/payments",
|
369
|
+
description="URL prefix for payment endpoints"
|
370
|
+
)
|
371
|
+
webhook_url_prefix: str = Field(
|
372
|
+
default="webhooks",
|
373
|
+
description="URL prefix for webhook endpoints"
|
374
|
+
)
|
375
|
+
|
376
|
+
@field_validator("providers")
|
377
|
+
@classmethod
|
378
|
+
def validate_providers(cls, v: List[PaymentProviderConfig]) -> List[PaymentProviderConfig]:
|
379
|
+
"""Validate payment provider configurations."""
|
380
|
+
if not isinstance(v, list):
|
381
|
+
raise ValueError("Providers must be a list")
|
382
|
+
|
383
|
+
# Check for duplicate provider names
|
384
|
+
names = [provider.name for provider in v]
|
385
|
+
if len(names) != len(set(names)):
|
386
|
+
raise ValueError("Duplicate provider names found")
|
387
|
+
|
388
|
+
# Validate at least one provider is configured if payments are enabled
|
389
|
+
enabled_providers = [provider.name for provider in v if provider.enabled]
|
390
|
+
if not enabled_providers:
|
391
|
+
logger.warning("No payment providers are enabled")
|
392
|
+
|
393
|
+
return v
|
394
|
+
|
395
|
+
@field_validator("url_prefix", "webhook_url_prefix")
|
396
|
+
@classmethod
|
397
|
+
def validate_url_prefixes(cls, v: str) -> str:
|
398
|
+
"""Validate URL prefixes."""
|
399
|
+
if not v:
|
400
|
+
raise ValueError("URL prefix cannot be empty")
|
401
|
+
|
402
|
+
# Remove leading/trailing slashes for consistency
|
403
|
+
v = v.strip("/")
|
404
|
+
|
405
|
+
# Basic validation
|
406
|
+
if not v.replace("/", "").replace("-", "").replace("_", "").isalnum():
|
407
|
+
raise ValueError("URL prefix must contain only alphanumeric characters, hyphens, underscores, and slashes")
|
408
|
+
|
409
|
+
return v
|
410
|
+
|
411
|
+
def get_enabled_providers(self) -> List[str]:
|
412
|
+
"""Get list of enabled payment providers."""
|
413
|
+
return [
|
414
|
+
name for name, config in self.providers.items()
|
415
|
+
if config.enabled
|
416
|
+
]
|
417
|
+
|
418
|
+
def get_provider_config(self, provider_name: str) -> Optional[PaymentProviderConfig]:
|
419
|
+
"""Get configuration for specific provider."""
|
420
|
+
return self.providers.get(provider_name)
|
421
|
+
|
422
|
+
def is_provider_enabled(self, provider_name: str) -> bool:
|
423
|
+
"""Check if specific provider is enabled."""
|
424
|
+
config = self.get_provider_config(provider_name)
|
425
|
+
return config is not None and config.enabled
|
426
|
+
|
427
|
+
def get_middleware_classes(self) -> List[str]:
|
428
|
+
"""Get list of middleware classes to enable."""
|
429
|
+
middleware = []
|
430
|
+
|
431
|
+
if not self.middleware_enabled:
|
432
|
+
return middleware
|
433
|
+
|
434
|
+
if self.enable_api_key_system:
|
435
|
+
middleware.append("django_cfg.apps.payments.middleware.APIAccessMiddleware")
|
436
|
+
|
437
|
+
if self.rate_limits.enabled:
|
438
|
+
middleware.append("django_cfg.apps.payments.middleware.RateLimitingMiddleware")
|
439
|
+
|
440
|
+
if self.enable_subscription_system:
|
441
|
+
middleware.append("django_cfg.apps.payments.middleware.UsageTrackingMiddleware")
|
442
|
+
|
443
|
+
# Add custom middleware
|
444
|
+
middleware.extend(self.custom_middleware)
|
445
|
+
|
446
|
+
return middleware
|
447
|
+
|
448
|
+
def should_enable_tasks(self) -> bool:
|
449
|
+
"""
|
450
|
+
Determine if background tasks should be enabled for payments.
|
451
|
+
|
452
|
+
Tasks are enabled if webhook processing is enabled.
|
453
|
+
"""
|
454
|
+
return self.enabled and self.enable_webhook_processing
|
455
|
+
|
456
|
+
|
457
|
+
# Helper function for easy provider configuration
|
458
|
+
def create_nowpayments_config(
|
459
|
+
api_key: str,
|
460
|
+
sandbox: bool = True,
|
461
|
+
ipn_secret: Optional[str] = None,
|
462
|
+
**kwargs
|
463
|
+
) -> NowPaymentsConfig:
|
464
|
+
"""Helper to create NowPayments configuration."""
|
465
|
+
return NowPaymentsConfig(
|
466
|
+
name="nowpayments",
|
467
|
+
api_key=SecretStr(api_key),
|
468
|
+
sandbox=sandbox,
|
469
|
+
ipn_secret=SecretStr(ipn_secret) if ipn_secret else None,
|
470
|
+
**kwargs
|
471
|
+
)
|
472
|
+
|
473
|
+
|
474
|
+
def create_cryptapi_config(
|
475
|
+
own_address: str,
|
476
|
+
callback_url: Optional[str] = None,
|
477
|
+
**kwargs
|
478
|
+
) -> CryptAPIConfig:
|
479
|
+
"""Helper to create CryptAPI configuration."""
|
480
|
+
return CryptAPIConfig(
|
481
|
+
name="cryptapi",
|
482
|
+
own_address=own_address,
|
483
|
+
callback_url=callback_url,
|
484
|
+
**kwargs
|
485
|
+
)
|
486
|
+
|
487
|
+
|
488
|
+
def create_stripe_config(
|
489
|
+
api_key: str,
|
490
|
+
publishable_key: Optional[str] = None,
|
491
|
+
webhook_endpoint_secret: Optional[str] = None,
|
492
|
+
sandbox: bool = True,
|
493
|
+
**kwargs
|
494
|
+
) -> StripeConfig:
|
495
|
+
"""Helper to create Stripe configuration."""
|
496
|
+
return StripeConfig(
|
497
|
+
name="stripe",
|
498
|
+
api_key=SecretStr(api_key),
|
499
|
+
publishable_key=publishable_key,
|
500
|
+
webhook_endpoint_secret=SecretStr(webhook_endpoint_secret) if webhook_endpoint_secret else None,
|
501
|
+
sandbox=sandbox,
|
502
|
+
**kwargs
|
503
|
+
)
|
504
|
+
|
505
|
+
|
506
|
+
def create_cryptomus_config(
|
507
|
+
api_key: str,
|
508
|
+
merchant_uuid: str,
|
509
|
+
sandbox: bool = True,
|
510
|
+
callback_url: Optional[str] = None,
|
511
|
+
success_url: Optional[str] = None,
|
512
|
+
fail_url: Optional[str] = None,
|
513
|
+
**kwargs
|
514
|
+
):
|
515
|
+
"""Helper to create Cryptomus configuration."""
|
516
|
+
# Import here to avoid circular imports
|
517
|
+
from django_cfg.apps.payments.config.providers import CryptomusConfig
|
518
|
+
|
519
|
+
return CryptomusConfig(
|
520
|
+
api_key=SecretStr(api_key),
|
521
|
+
merchant_uuid=merchant_uuid,
|
522
|
+
sandbox=sandbox,
|
523
|
+
callback_url=callback_url,
|
524
|
+
success_url=success_url,
|
525
|
+
fail_url=fail_url,
|
526
|
+
**kwargs
|
527
|
+
)
|
528
|
+
|
529
|
+
|
530
|
+
__all__ = [
|
531
|
+
"PaymentsConfig",
|
532
|
+
"PaymentProviderConfig",
|
533
|
+
"NowPaymentsConfig",
|
534
|
+
"CryptAPIConfig",
|
535
|
+
"StripeConfig",
|
536
|
+
"SecuritySettings",
|
537
|
+
"RateLimitSettings",
|
538
|
+
"NotificationSettings",
|
539
|
+
"SubscriptionSettings",
|
540
|
+
"PaymentProvider",
|
541
|
+
"BillingPeriod",
|
542
|
+
"create_nowpayments_config",
|
543
|
+
"create_cryptapi_config",
|
544
|
+
"create_stripe_config",
|
545
|
+
"create_cryptomus_config",
|
546
|
+
]
|
django_cfg/models/revolution.py
CHANGED
@@ -93,7 +93,7 @@ class ExtendedRevolutionConfig(BaseDjangoRevolutionConfig):
|
|
93
93
|
knowbase_enabled = base_module.is_knowbase_enabled()
|
94
94
|
agents_enabled = base_module.is_agents_enabled()
|
95
95
|
tasks_enabled = base_module.should_enable_tasks()
|
96
|
-
payments_enabled = base_module.
|
96
|
+
payments_enabled = base_module.is_payments_enabled()
|
97
97
|
|
98
98
|
# Add Support zone if enabled
|
99
99
|
default_support_zone = 'cfg_support'
|
django_cfg/models/tasks.py
CHANGED
@@ -428,8 +428,57 @@ class TaskConfig(BaseModel, BaseCfgAutoModule):
|
|
428
428
|
|
429
429
|
# === Utility Functions ===
|
430
430
|
|
431
|
+
def get_smart_queues(debug: bool = False) -> List[str]:
|
432
|
+
"""
|
433
|
+
Get smart default queues based on enabled modules.
|
434
|
+
|
435
|
+
Automatically detects which django-cfg modules are enabled and adds
|
436
|
+
their corresponding queues to the default queue list.
|
437
|
+
|
438
|
+
Args:
|
439
|
+
debug: Whether running in debug mode (affects base queues)
|
440
|
+
|
441
|
+
Returns:
|
442
|
+
List of queue names appropriate for enabled modules
|
443
|
+
"""
|
444
|
+
# Base queues
|
445
|
+
if debug:
|
446
|
+
base_queues = ["default"]
|
447
|
+
else:
|
448
|
+
base_queues = ["critical", "high", "default", "low", "background"]
|
449
|
+
|
450
|
+
# Try to detect enabled modules and add their queues
|
451
|
+
try:
|
452
|
+
from django_cfg.modules.base import BaseCfgModule
|
453
|
+
base_module = BaseCfgModule()
|
454
|
+
|
455
|
+
# Check for knowbase module (requires "knowbase" queue)
|
456
|
+
if base_module.is_knowbase_enabled():
|
457
|
+
if "knowbase" not in base_queues:
|
458
|
+
base_queues.append("knowbase")
|
459
|
+
|
460
|
+
# Check for payments module (requires "payments" queue)
|
461
|
+
if base_module.is_payments_enabled():
|
462
|
+
if "payments" not in base_queues:
|
463
|
+
base_queues.append("payments")
|
464
|
+
|
465
|
+
# Check for agents module (may require "agents" queue in future)
|
466
|
+
if base_module.is_agents_enabled():
|
467
|
+
if "agents" not in base_queues:
|
468
|
+
base_queues.append("agents")
|
469
|
+
|
470
|
+
logger.info(f"🎯 Smart queue detection: {base_queues}")
|
471
|
+
|
472
|
+
except Exception as e:
|
473
|
+
logger.warning(f"Failed to auto-detect queues, using defaults: {e}")
|
474
|
+
|
475
|
+
return base_queues
|
476
|
+
|
477
|
+
|
431
478
|
def get_default_task_config(debug: bool = False) -> TaskConfig:
|
432
479
|
"""Get default task configuration based on environment."""
|
480
|
+
smart_queues = get_smart_queues(debug)
|
481
|
+
|
433
482
|
if debug:
|
434
483
|
# Development defaults
|
435
484
|
return TaskConfig(
|
@@ -437,7 +486,7 @@ def get_default_task_config(debug: bool = False) -> TaskConfig:
|
|
437
486
|
processes=2,
|
438
487
|
threads=4,
|
439
488
|
prometheus_enabled=False,
|
440
|
-
queues=
|
489
|
+
queues=smart_queues,
|
441
490
|
),
|
442
491
|
worker=WorkerConfig(
|
443
492
|
log_level="DEBUG",
|
@@ -451,7 +500,7 @@ def get_default_task_config(debug: bool = False) -> TaskConfig:
|
|
451
500
|
processes=8,
|
452
501
|
threads=16,
|
453
502
|
prometheus_enabled=True,
|
454
|
-
queues=
|
503
|
+
queues=smart_queues,
|
455
504
|
),
|
456
505
|
worker=WorkerConfig(
|
457
506
|
log_level="INFO",
|
django_cfg/modules/base.py
CHANGED
@@ -77,7 +77,7 @@ class BaseCfgModule(ABC):
|
|
77
77
|
"""
|
78
78
|
self._config = config
|
79
79
|
|
80
|
-
def _get_config_key(self, key: str, default: Any) ->
|
80
|
+
def _get_config_key(self, key: str, default: Any) -> Any:
|
81
81
|
"""
|
82
82
|
Get a key from the configuration instance.
|
83
83
|
|
@@ -92,14 +92,14 @@ class BaseCfgModule(ABC):
|
|
92
92
|
# If config is available, get the key
|
93
93
|
if config is not None:
|
94
94
|
result = getattr(config, key, default)
|
95
|
-
return
|
95
|
+
return result
|
96
96
|
|
97
97
|
# Fallback to default if no config available
|
98
|
-
return
|
98
|
+
return default
|
99
99
|
|
100
100
|
except Exception:
|
101
101
|
# Return default on any error
|
102
|
-
return
|
102
|
+
return default
|
103
103
|
|
104
104
|
def is_support_enabled(self) -> bool:
|
105
105
|
"""
|
@@ -175,14 +175,20 @@ class BaseCfgModule(ABC):
|
|
175
175
|
"""
|
176
176
|
return self._get_config_key('enable_maintenance', False)
|
177
177
|
|
178
|
-
def
|
178
|
+
def is_payments_enabled(self) -> bool:
|
179
179
|
"""
|
180
180
|
Check if django-cfg Payments is enabled.
|
181
181
|
|
182
182
|
Returns:
|
183
183
|
True if Payments is enabled, False otherwise
|
184
184
|
"""
|
185
|
-
|
185
|
+
payments_config = self._get_config_key('payments', None)
|
186
|
+
|
187
|
+
# Only handle PaymentsConfig model
|
188
|
+
if payments_config and hasattr(payments_config, 'enabled'):
|
189
|
+
return payments_config.enabled
|
190
|
+
|
191
|
+
return False
|
186
192
|
|
187
193
|
|
188
194
|
# Export the base class
|