django-cfg 1.2.31__py3-none-any.whl → 1.3.1__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/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -10
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +172 -148
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -285
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +346 -467
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +40 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -146
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
- django_cfg/apps/payments/managers/__init__.py +0 -23
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -63
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -461
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -63
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -122
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -66
- django_cfg/core/integration.py +0 -160
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,12 @@
|
|
1
1
|
"""
|
2
|
-
Universal
|
2
|
+
Universal Payment System v2.0 - Models Package.
|
3
3
|
|
4
|
-
|
4
|
+
Simplified models focused on NowPayments with extensible architecture.
|
5
|
+
All models follow the data typing requirements: Django ORM for database layer.
|
5
6
|
"""
|
6
7
|
|
7
8
|
# Base models
|
8
|
-
from .base import
|
9
|
+
from .base import UUIDTimestampedModel
|
9
10
|
|
10
11
|
# Currency models
|
11
12
|
from .currencies import Currency, Network, ProviderCurrency
|
@@ -17,7 +18,7 @@ from .payments import UniversalPayment
|
|
17
18
|
from .balance import UserBalance, Transaction
|
18
19
|
|
19
20
|
# Subscription models
|
20
|
-
from .subscriptions import
|
21
|
+
from .subscriptions import Subscription, EndpointGroup
|
21
22
|
|
22
23
|
# Tariff models
|
23
24
|
from .tariffs import Tariff, TariffEndpointGroup
|
@@ -25,44 +26,38 @@ from .tariffs import Tariff, TariffEndpointGroup
|
|
25
26
|
# API Keys
|
26
27
|
from .api_keys import APIKey
|
27
28
|
|
28
|
-
#
|
29
|
-
from .events import PaymentEvent
|
30
|
-
|
31
|
-
# TextChoices classes for external use (accessing inner classes)
|
32
|
-
CurrencyType = Currency.CurrencyType
|
29
|
+
# Export TextChoices for external use
|
33
30
|
PaymentStatus = UniversalPayment.PaymentStatus
|
34
31
|
PaymentProvider = UniversalPayment.PaymentProvider
|
32
|
+
CurrencyType = Currency.CurrencyType
|
35
33
|
TransactionType = Transaction.TransactionType
|
36
34
|
SubscriptionStatus = Subscription.SubscriptionStatus
|
37
35
|
SubscriptionTier = Subscription.SubscriptionTier
|
38
|
-
EventType = PaymentEvent.EventType
|
39
36
|
|
40
37
|
__all__ = [
|
41
38
|
# Base
|
42
|
-
'
|
39
|
+
'UUIDTimestampedModel',
|
43
40
|
|
44
41
|
# Currencies
|
45
42
|
'Currency',
|
46
43
|
'Network',
|
47
44
|
'ProviderCurrency',
|
48
45
|
|
49
|
-
# Models
|
46
|
+
# Core Models
|
50
47
|
'UniversalPayment',
|
51
48
|
'UserBalance',
|
52
49
|
'Transaction',
|
53
|
-
'EndpointGroup',
|
54
50
|
'Subscription',
|
51
|
+
'EndpointGroup',
|
55
52
|
'Tariff',
|
56
53
|
'TariffEndpointGroup',
|
57
54
|
'APIKey',
|
58
|
-
'PaymentEvent',
|
59
55
|
|
60
56
|
# TextChoices
|
61
|
-
'CurrencyType',
|
62
57
|
'PaymentStatus',
|
63
|
-
'PaymentProvider',
|
58
|
+
'PaymentProvider',
|
59
|
+
'CurrencyType',
|
64
60
|
'TransactionType',
|
65
61
|
'SubscriptionStatus',
|
66
62
|
'SubscriptionTier',
|
67
|
-
|
68
|
-
]
|
63
|
+
]
|
@@ -1,9 +1,14 @@
|
|
1
1
|
"""
|
2
|
-
API
|
2
|
+
API Key models for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Handles API key management and access control.
|
3
5
|
"""
|
4
6
|
|
7
|
+
import secrets
|
5
8
|
from django.db import models
|
6
9
|
from django.contrib.auth import get_user_model
|
10
|
+
from django.core.validators import MinLengthValidator
|
11
|
+
from django.core.exceptions import ValidationError
|
7
12
|
from django.utils import timezone
|
8
13
|
from .base import UUIDTimestampedModel
|
9
14
|
|
@@ -11,86 +16,159 @@ User = get_user_model()
|
|
11
16
|
|
12
17
|
|
13
18
|
class APIKey(UUIDTimestampedModel):
|
14
|
-
"""
|
19
|
+
"""
|
20
|
+
API Key model for user authentication and access control.
|
21
|
+
|
22
|
+
Provides secure API access with usage tracking and rate limiting.
|
23
|
+
"""
|
15
24
|
|
16
25
|
user = models.ForeignKey(
|
17
26
|
User,
|
18
27
|
on_delete=models.CASCADE,
|
19
28
|
related_name='api_keys',
|
20
|
-
help_text="API key
|
29
|
+
help_text="User who owns this API key"
|
21
30
|
)
|
22
31
|
|
23
|
-
# Key details
|
24
32
|
name = models.CharField(
|
25
33
|
max_length=100,
|
26
|
-
help_text="Human-readable key
|
34
|
+
help_text="Human-readable name for this API key"
|
27
35
|
)
|
28
|
-
|
29
|
-
|
36
|
+
|
37
|
+
key = models.CharField(
|
38
|
+
max_length=64,
|
30
39
|
unique=True,
|
31
|
-
|
32
|
-
|
33
|
-
key_prefix = models.CharField(
|
34
|
-
max_length=20,
|
35
|
-
help_text="Key prefix for identification"
|
40
|
+
validators=[MinLengthValidator(32)],
|
41
|
+
help_text="The actual API key (auto-generated)"
|
36
42
|
)
|
37
43
|
|
38
|
-
#
|
44
|
+
# Access control
|
39
45
|
is_active = models.BooleanField(
|
40
46
|
default=True,
|
41
|
-
help_text="
|
47
|
+
help_text="Whether this API key is active"
|
42
48
|
)
|
43
49
|
|
44
50
|
# Usage tracking
|
45
|
-
|
51
|
+
total_requests = models.PositiveIntegerField(
|
52
|
+
default=0,
|
53
|
+
help_text="Total number of requests made with this key"
|
54
|
+
)
|
55
|
+
|
56
|
+
last_used_at = models.DateTimeField(
|
46
57
|
null=True,
|
47
58
|
blank=True,
|
48
|
-
help_text="
|
49
|
-
)
|
50
|
-
usage_count = models.PositiveBigIntegerField(
|
51
|
-
default=0,
|
52
|
-
help_text="Total usage count"
|
59
|
+
help_text="When this API key was last used"
|
53
60
|
)
|
54
61
|
|
55
|
-
#
|
62
|
+
# Expiration
|
56
63
|
expires_at = models.DateTimeField(
|
57
64
|
null=True,
|
58
65
|
blank=True,
|
59
|
-
help_text="
|
66
|
+
help_text="When this API key expires (null = never expires)"
|
67
|
+
)
|
68
|
+
|
69
|
+
# IP restrictions
|
70
|
+
allowed_ips = models.TextField(
|
71
|
+
blank=True,
|
72
|
+
help_text="Comma-separated list of allowed IP addresses (empty = any IP)"
|
60
73
|
)
|
61
74
|
|
62
|
-
#
|
63
|
-
from
|
75
|
+
# Manager
|
76
|
+
from .managers.api_key_managers import APIKeyManager
|
64
77
|
objects = APIKeyManager()
|
65
78
|
|
66
79
|
class Meta:
|
67
|
-
db_table = '
|
68
|
-
verbose_name =
|
69
|
-
verbose_name_plural =
|
80
|
+
db_table = 'payments_api_keys'
|
81
|
+
verbose_name = 'API Key'
|
82
|
+
verbose_name_plural = 'API Keys'
|
83
|
+
ordering = ['-created_at']
|
70
84
|
indexes = [
|
85
|
+
models.Index(fields=['key']),
|
71
86
|
models.Index(fields=['user', 'is_active']),
|
72
|
-
models.Index(fields=['key_value']),
|
73
|
-
models.Index(fields=['key_prefix']),
|
74
|
-
models.Index(fields=['last_used']),
|
75
87
|
models.Index(fields=['expires_at']),
|
76
88
|
]
|
77
|
-
ordering = ['-created_at']
|
78
89
|
|
79
90
|
def __str__(self):
|
80
|
-
return f"
|
91
|
+
return f"{self.user.username} - {self.name}"
|
81
92
|
|
82
|
-
def
|
83
|
-
"""
|
84
|
-
if not self.
|
85
|
-
|
86
|
-
|
93
|
+
def save(self, *args, **kwargs):
|
94
|
+
"""Override save to generate API key."""
|
95
|
+
if not self.key:
|
96
|
+
self.key = self.generate_api_key()
|
97
|
+
super().save(*args, **kwargs)
|
98
|
+
|
99
|
+
def clean(self):
|
100
|
+
"""Validate API key data."""
|
87
101
|
if self.expires_at and self.expires_at <= timezone.now():
|
102
|
+
raise ValidationError("Expiration time must be in the future")
|
103
|
+
|
104
|
+
@staticmethod
|
105
|
+
def generate_api_key() -> str:
|
106
|
+
"""Generate a secure API key."""
|
107
|
+
return secrets.token_urlsafe(32)
|
108
|
+
|
109
|
+
@property
|
110
|
+
def is_expired(self) -> bool:
|
111
|
+
"""Check if API key is expired."""
|
112
|
+
if not self.expires_at:
|
88
113
|
return False
|
114
|
+
return timezone.now() > self.expires_at
|
115
|
+
|
116
|
+
@property
|
117
|
+
def is_valid(self) -> bool:
|
118
|
+
"""Check if API key is valid (active and not expired)."""
|
119
|
+
return self.is_active and not self.is_expired
|
120
|
+
|
121
|
+
@property
|
122
|
+
def masked_key(self) -> str:
|
123
|
+
"""Get masked version of API key for display."""
|
124
|
+
if len(self.key) < 8:
|
125
|
+
return self.key
|
126
|
+
return f"{self.key[:4]}...{self.key[-4:]}"
|
127
|
+
|
128
|
+
@property
|
129
|
+
def days_until_expiry(self) -> int:
|
130
|
+
"""Get days until expiration."""
|
131
|
+
if not self.expires_at:
|
132
|
+
return -1 # Never expires
|
133
|
+
if self.is_expired:
|
134
|
+
return 0
|
135
|
+
delta = self.expires_at - timezone.now()
|
136
|
+
return max(0, delta.days)
|
137
|
+
|
138
|
+
def is_ip_allowed(self, ip_address: str) -> bool:
|
139
|
+
"""
|
140
|
+
Check if IP address is allowed to use this API key.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
ip_address: IP address to check
|
89
144
|
|
90
|
-
|
145
|
+
Returns:
|
146
|
+
bool: True if IP is allowed
|
147
|
+
"""
|
148
|
+
if not self.allowed_ips.strip():
|
149
|
+
return True # No restrictions
|
150
|
+
|
151
|
+
allowed_list = [ip.strip() for ip in self.allowed_ips.split(',') if ip.strip()]
|
152
|
+
return ip_address in allowed_list
|
153
|
+
|
154
|
+
def increment_usage(self, ip_address: str = None):
|
155
|
+
"""Increment usage counter (delegates to manager)."""
|
156
|
+
return self.__class__.objects.increment_api_key_usage(self, ip_address)
|
157
|
+
|
158
|
+
def deactivate(self, reason: str = None):
|
159
|
+
"""Deactivate this API key (delegates to manager)."""
|
160
|
+
return self.__class__.objects.deactivate_api_key(self, reason)
|
161
|
+
|
162
|
+
def extend_expiry(self, days: int):
|
163
|
+
"""Extend API key expiration (delegates to manager)."""
|
164
|
+
return self.__class__.objects.extend_api_key_expiry(self, days)
|
165
|
+
|
166
|
+
@classmethod
|
167
|
+
def create_for_user(cls, user, name="Default API Key", expires_in_days=None):
|
168
|
+
"""Create new API key for user (delegates to manager)."""
|
169
|
+
return cls.objects.create_api_key_for_user(user, name, expires_in_days)
|
91
170
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
self.save(update_fields=['usage_count', 'last_used'])
|
171
|
+
@classmethod
|
172
|
+
def get_valid_key(cls, key_value: str):
|
173
|
+
"""Get valid API key by key value (delegates to manager)."""
|
174
|
+
return cls.objects.get_valid_api_key(key_value)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
"""
|
2
|
-
Balance and transaction models for the
|
2
|
+
Balance and transaction models for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Handles user balances and transaction history with atomic operations.
|
3
5
|
"""
|
4
6
|
|
5
7
|
from django.db import models, transaction
|
@@ -7,203 +9,236 @@ from django.contrib.auth import get_user_model
|
|
7
9
|
from django.core.validators import MinValueValidator
|
8
10
|
from django.core.exceptions import ValidationError
|
9
11
|
from django.utils import timezone
|
10
|
-
from .base import UUIDTimestampedModel
|
12
|
+
from .base import UUIDTimestampedModel
|
11
13
|
|
12
14
|
User = get_user_model()
|
13
15
|
|
14
16
|
|
15
|
-
class UserBalance(
|
16
|
-
"""
|
17
|
+
class UserBalance(models.Model):
|
18
|
+
"""
|
19
|
+
User balance model with atomic operations.
|
20
|
+
|
21
|
+
Tracks user balance in USD (float for performance as per requirements).
|
22
|
+
All balance updates are handled via Django signals for consistency.
|
23
|
+
"""
|
17
24
|
|
18
25
|
user = models.OneToOneField(
|
19
26
|
User,
|
20
27
|
on_delete=models.CASCADE,
|
21
|
-
related_name='
|
28
|
+
related_name='payment_balance',
|
22
29
|
help_text="User who owns this balance"
|
23
30
|
)
|
24
|
-
|
25
|
-
|
26
|
-
validators=[MinValueValidator(0.0)],
|
27
|
-
help_text="Current balance in USD"
|
28
|
-
)
|
29
|
-
reserved_usd = models.FloatField(
|
31
|
+
|
32
|
+
balance_usd = models.FloatField(
|
30
33
|
default=0.0,
|
31
34
|
validators=[MinValueValidator(0.0)],
|
32
|
-
help_text="
|
35
|
+
help_text="Current balance in USD (float for performance)"
|
33
36
|
)
|
34
|
-
|
37
|
+
|
38
|
+
# Tracking fields
|
39
|
+
total_deposited = models.FloatField(
|
35
40
|
default=0.0,
|
36
41
|
validators=[MinValueValidator(0.0)],
|
37
|
-
help_text="Total amount
|
42
|
+
help_text="Total amount deposited (lifetime)"
|
38
43
|
)
|
44
|
+
|
39
45
|
total_spent = models.FloatField(
|
40
46
|
default=0.0,
|
41
47
|
validators=[MinValueValidator(0.0)],
|
42
48
|
help_text="Total amount spent (lifetime)"
|
43
49
|
)
|
50
|
+
|
44
51
|
last_transaction_at = models.DateTimeField(
|
45
52
|
null=True,
|
46
53
|
blank=True,
|
47
54
|
help_text="When the last transaction occurred"
|
48
55
|
)
|
49
|
-
|
50
|
-
#
|
51
|
-
|
56
|
+
|
57
|
+
# Timestamps
|
58
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
59
|
+
updated_at = models.DateTimeField(auto_now=True)
|
60
|
+
|
61
|
+
# Manager
|
62
|
+
from .managers.balance_managers import UserBalanceManager
|
52
63
|
objects = UserBalanceManager()
|
53
|
-
|
64
|
+
|
54
65
|
class Meta:
|
55
|
-
db_table = '
|
56
|
-
verbose_name =
|
57
|
-
verbose_name_plural =
|
66
|
+
db_table = 'payments_user_balances'
|
67
|
+
verbose_name = 'User Balance'
|
68
|
+
verbose_name_plural = 'User Balances'
|
58
69
|
indexes = [
|
59
|
-
models.Index(fields=['
|
60
|
-
models.Index(fields=['amount_usd']),
|
70
|
+
models.Index(fields=['balance_usd']),
|
61
71
|
models.Index(fields=['last_transaction_at']),
|
62
72
|
]
|
63
|
-
|
73
|
+
constraints = [
|
74
|
+
models.CheckConstraint(
|
75
|
+
check=models.Q(balance_usd__gte=0.0),
|
76
|
+
name='balance_non_negative_check'
|
77
|
+
),
|
78
|
+
]
|
79
|
+
|
64
80
|
def __str__(self):
|
65
|
-
return f"{self.user.
|
66
|
-
|
81
|
+
return f"{self.user.username}: ${self.balance_usd:.2f}"
|
82
|
+
|
83
|
+
def clean(self):
|
84
|
+
"""Validate balance data."""
|
85
|
+
if self.balance_usd < 0:
|
86
|
+
raise ValidationError("Balance cannot be negative")
|
87
|
+
|
67
88
|
@property
|
68
|
-
def
|
69
|
-
"""
|
70
|
-
return self.
|
71
|
-
|
89
|
+
def balance_display(self) -> str:
|
90
|
+
"""Formatted balance display."""
|
91
|
+
return f"${self.balance_usd:.2f} USD"
|
92
|
+
|
72
93
|
@property
|
73
|
-
def
|
74
|
-
"""Check if
|
75
|
-
return self.
|
94
|
+
def is_empty(self) -> bool:
|
95
|
+
"""Check if balance is zero."""
|
96
|
+
return self.balance_usd == 0.0
|
97
|
+
|
98
|
+
@property
|
99
|
+
def has_transactions(self) -> bool:
|
100
|
+
"""Check if user has any transactions."""
|
101
|
+
return self.last_transaction_at is not None
|
102
|
+
|
103
|
+
def add_funds(self, amount: float, transaction_type: str = 'deposit',
|
104
|
+
description: str = None, payment_id: str = None) -> 'Transaction':
|
105
|
+
"""Add funds to balance (delegates to manager)."""
|
106
|
+
return self.__class__.objects.add_funds_to_user(
|
107
|
+
self.user, amount, transaction_type, description, payment_id
|
108
|
+
)
|
109
|
+
|
110
|
+
def subtract_funds(self, amount: float, transaction_type: str = 'withdrawal',
|
111
|
+
description: str = None, payment_id: str = None) -> 'Transaction':
|
112
|
+
"""Subtract funds from balance (delegates to manager)."""
|
113
|
+
return self.__class__.objects.subtract_funds_from_user(
|
114
|
+
self.user, amount, transaction_type, description, payment_id
|
115
|
+
)
|
116
|
+
|
117
|
+
@classmethod
|
118
|
+
def get_or_create_for_user(cls, user: User) -> 'UserBalance':
|
119
|
+
"""Get or create balance for user (delegates to manager)."""
|
120
|
+
return cls.objects.get_or_create_for_user(user)
|
76
121
|
|
77
|
-
def can_debit(self, amount: float) -> bool:
|
78
|
-
"""Check if user can be debited the specified amount."""
|
79
|
-
return self.amount_usd >= amount
|
80
122
|
|
81
|
-
def get_transaction_summary(self):
|
82
|
-
"""Get transaction summary for this user."""
|
83
|
-
transactions = self.user.transactions.all()
|
84
|
-
return {
|
85
|
-
'total_transactions': transactions.count(),
|
86
|
-
'total_payments': transactions.filter(transaction_type=Transaction.TransactionType.PAYMENT).count(),
|
87
|
-
'total_subscriptions': transactions.filter(transaction_type=Transaction.TransactionType.SUBSCRIPTION).count(),
|
88
|
-
'total_refunds': transactions.filter(transaction_type=Transaction.TransactionType.REFUND).count(),
|
89
|
-
}
|
90
123
|
|
91
124
|
|
92
125
|
class Transaction(UUIDTimestampedModel):
|
93
|
-
"""
|
126
|
+
"""
|
127
|
+
Transaction record for balance changes.
|
128
|
+
|
129
|
+
Immutable record of all balance changes with full audit trail.
|
130
|
+
"""
|
94
131
|
|
95
132
|
class TransactionType(models.TextChoices):
|
133
|
+
DEPOSIT = "deposit", "Deposit"
|
134
|
+
WITHDRAWAL = "withdrawal", "Withdrawal"
|
96
135
|
PAYMENT = "payment", "Payment"
|
97
|
-
SUBSCRIPTION = "subscription", "Subscription"
|
98
136
|
REFUND = "refund", "Refund"
|
99
|
-
CREDIT = "credit", "Credit"
|
100
|
-
DEBIT = "debit", "Debit"
|
101
|
-
HOLD = "hold", "Hold"
|
102
|
-
RELEASE = "release", "Release"
|
103
137
|
FEE = "fee", "Fee"
|
138
|
+
BONUS = "bonus", "Bonus"
|
104
139
|
ADJUSTMENT = "adjustment", "Adjustment"
|
105
140
|
|
106
141
|
user = models.ForeignKey(
|
107
142
|
User,
|
108
143
|
on_delete=models.CASCADE,
|
109
|
-
related_name='
|
110
|
-
help_text="User who
|
111
|
-
)
|
112
|
-
amount_usd = models.FloatField(
|
113
|
-
help_text="Transaction amount in USD (positive for credits, negative for debits)"
|
144
|
+
related_name='payment_transactions',
|
145
|
+
help_text="User who owns this transaction"
|
114
146
|
)
|
147
|
+
|
115
148
|
transaction_type = models.CharField(
|
116
149
|
max_length=20,
|
117
150
|
choices=TransactionType.choices,
|
118
151
|
help_text="Type of transaction"
|
119
152
|
)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
help_text="User balance before this transaction"
|
153
|
+
|
154
|
+
# Amount in USD (float for performance, positive for credits, negative for debits)
|
155
|
+
amount_usd = models.FloatField(
|
156
|
+
help_text="Transaction amount in USD (positive=credit, negative=debit)"
|
125
157
|
)
|
158
|
+
|
126
159
|
balance_after = models.FloatField(
|
160
|
+
validators=[MinValueValidator(0.0)],
|
127
161
|
help_text="User balance after this transaction"
|
128
162
|
)
|
129
163
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
payment = models.ForeignKey(
|
134
|
-
UniversalPayment,
|
135
|
-
on_delete=models.SET_NULL,
|
136
|
-
null=True,
|
137
|
-
blank=True,
|
138
|
-
related_name='transactions',
|
139
|
-
help_text="Related payment (if applicable)"
|
140
|
-
)
|
141
|
-
subscription = models.ForeignKey(
|
142
|
-
Subscription,
|
143
|
-
on_delete=models.SET_NULL,
|
164
|
+
# Reference to related payment
|
165
|
+
payment_id = models.CharField(
|
166
|
+
max_length=100,
|
144
167
|
null=True,
|
145
168
|
blank=True,
|
146
|
-
|
147
|
-
help_text="Related
|
169
|
+
db_index=True,
|
170
|
+
help_text="Related payment ID (if applicable)"
|
148
171
|
)
|
149
172
|
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
null=True,
|
154
|
-
blank=True,
|
155
|
-
help_text="External reference ID"
|
173
|
+
# Transaction details
|
174
|
+
description = models.TextField(
|
175
|
+
help_text="Transaction description"
|
156
176
|
)
|
177
|
+
|
178
|
+
# Metadata for additional information
|
157
179
|
metadata = models.JSONField(
|
158
180
|
default=dict,
|
181
|
+
blank=True,
|
159
182
|
help_text="Additional transaction metadata"
|
160
183
|
)
|
161
|
-
|
184
|
+
|
185
|
+
# Manager
|
186
|
+
from .managers.balance_managers import TransactionManager
|
187
|
+
objects = TransactionManager()
|
188
|
+
|
162
189
|
class Meta:
|
163
|
-
db_table = '
|
164
|
-
verbose_name =
|
165
|
-
verbose_name_plural =
|
190
|
+
db_table = 'payments_transactions'
|
191
|
+
verbose_name = 'Transaction'
|
192
|
+
verbose_name_plural = 'Transactions'
|
193
|
+
ordering = ['-created_at']
|
166
194
|
indexes = [
|
167
195
|
models.Index(fields=['user', 'created_at']),
|
168
|
-
models.Index(fields=['transaction_type']),
|
196
|
+
models.Index(fields=['transaction_type', 'created_at']),
|
197
|
+
models.Index(fields=['payment_id']),
|
169
198
|
models.Index(fields=['amount_usd']),
|
170
|
-
models.Index(fields=['created_at']),
|
171
|
-
models.Index(fields=['reference_id']),
|
172
199
|
]
|
173
|
-
|
174
|
-
|
200
|
+
|
175
201
|
def __str__(self):
|
176
202
|
sign = "+" if self.amount_usd >= 0 else ""
|
177
|
-
return f"{self.user.
|
178
|
-
|
203
|
+
return f"{self.user.username}: {sign}${self.amount_usd:.2f} ({self.transaction_type})"
|
204
|
+
|
205
|
+
def clean(self):
|
206
|
+
"""Validate transaction data."""
|
207
|
+
if self.balance_after < 0:
|
208
|
+
raise ValidationError("Balance after transaction cannot be negative")
|
209
|
+
|
179
210
|
@property
|
180
211
|
def is_credit(self) -> bool:
|
181
212
|
"""Check if this is a credit transaction."""
|
182
213
|
return self.amount_usd > 0
|
183
|
-
|
214
|
+
|
184
215
|
@property
|
185
216
|
def is_debit(self) -> bool:
|
186
217
|
"""Check if this is a debit transaction."""
|
187
218
|
return self.amount_usd < 0
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
219
|
+
|
220
|
+
@property
|
221
|
+
def amount_display(self) -> str:
|
222
|
+
"""Formatted amount display."""
|
223
|
+
sign = "+" if self.amount_usd >= 0 else ""
|
224
|
+
return f"{sign}${abs(self.amount_usd):.2f}"
|
225
|
+
|
226
|
+
@property
|
227
|
+
def type_color(self) -> str:
|
228
|
+
"""Get color for transaction type display."""
|
229
|
+
colors = {
|
230
|
+
self.TransactionType.DEPOSIT: 'success',
|
231
|
+
self.TransactionType.PAYMENT: 'primary',
|
232
|
+
self.TransactionType.WITHDRAWAL: 'warning',
|
233
|
+
self.TransactionType.REFUND: 'info',
|
234
|
+
self.TransactionType.FEE: 'secondary',
|
235
|
+
self.TransactionType.BONUS: 'success',
|
236
|
+
self.TransactionType.ADJUSTMENT: 'secondary',
|
237
|
+
}
|
238
|
+
return colors.get(self.transaction_type, 'secondary')
|
239
|
+
|
206
240
|
def save(self, *args, **kwargs):
|
207
|
-
"""Override save to
|
208
|
-
self.
|
241
|
+
"""Override save to ensure immutability."""
|
242
|
+
if self.pk:
|
243
|
+
raise ValidationError("Transactions are immutable and cannot be modified")
|
209
244
|
super().save(*args, **kwargs)
|