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.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {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 payments models package.
2
+ Universal Payment System v2.0 - Models Package.
3
3
 
4
- Django ORM models for the universal payments system.
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 TimestampedModel
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 EndpointGroup, Subscription
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
- # Event sourcing
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
- 'TimestampedModel',
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
- 'EventType',
68
- ]
63
+ ]
@@ -1,9 +1,14 @@
1
1
  """
2
- API key models for the universal payments system.
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
- """API keys for authentication and usage tracking."""
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 owner"
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 name"
34
+ help_text="Human-readable name for this API key"
27
35
  )
28
- key_value = models.CharField(
29
- max_length=255,
36
+
37
+ key = models.CharField(
38
+ max_length=64,
30
39
  unique=True,
31
- help_text="API key value (plain text)"
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
- # Permissions
44
+ # Access control
39
45
  is_active = models.BooleanField(
40
46
  default=True,
41
- help_text="Is key active"
47
+ help_text="Whether this API key is active"
42
48
  )
43
49
 
44
50
  # Usage tracking
45
- last_used = models.DateTimeField(
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="Last usage timestamp"
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
- # Lifecycle
62
+ # Expiration
56
63
  expires_at = models.DateTimeField(
57
64
  null=True,
58
65
  blank=True,
59
- help_text="Key expiration"
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
- # Import and assign manager
63
- from ..managers import APIKeyManager
75
+ # Manager
76
+ from .managers.api_key_managers import APIKeyManager
64
77
  objects = APIKeyManager()
65
78
 
66
79
  class Meta:
67
- db_table = 'api_keys'
68
- verbose_name = "API Key"
69
- verbose_name_plural = "API Keys"
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"API Key: {self.name} ({self.key_prefix}***)"
91
+ return f"{self.user.username} - {self.name}"
81
92
 
82
- def is_valid(self) -> bool:
83
- """Check if API key is valid."""
84
- if not self.is_active:
85
- return False
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
- return True
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
- def record_usage(self):
93
- """Record API key usage."""
94
- self.usage_count += 1
95
- self.last_used = timezone.now()
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 universal payments system.
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, TimestampedModel
12
+ from .base import UUIDTimestampedModel
11
13
 
12
14
  User = get_user_model()
13
15
 
14
16
 
15
- class UserBalance(TimestampedModel):
16
- """User balance model for tracking USD funds."""
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='balance',
28
+ related_name='payment_balance',
22
29
  help_text="User who owns this balance"
23
30
  )
24
- amount_usd = models.FloatField(
25
- default=0.0,
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="Reserved balance in USD (for pending transactions)"
35
+ help_text="Current balance in USD (float for performance)"
33
36
  )
34
- total_earned = models.FloatField(
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 earned (lifetime)"
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
- # Import and assign manager
51
- from ..managers import UserBalanceManager
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 = 'user_balances'
56
- verbose_name = "User Balance"
57
- verbose_name_plural = "User Balances"
66
+ db_table = 'payments_user_balances'
67
+ verbose_name = 'User Balance'
68
+ verbose_name_plural = 'User Balances'
58
69
  indexes = [
59
- models.Index(fields=['user']),
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.email} - ${self.amount_usd}"
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 total_balance(self) -> float:
69
- """Get total balance (available + reserved)."""
70
- return self.amount_usd + self.reserved_usd
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 has_sufficient_funds(self) -> bool:
74
- """Check if user has sufficient available funds."""
75
- return self.amount_usd > 0
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
- """Transaction history model."""
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='transactions',
110
- help_text="User who made this transaction"
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
- description = models.TextField(
121
- help_text="Human-readable description of the transaction"
122
- )
123
- balance_before = models.FloatField(
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
- # Related objects (nullable for flexibility)
131
- from .payments import UniversalPayment
132
- from .subscriptions import Subscription
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
- related_name='transactions',
147
- help_text="Related subscription (if applicable)"
169
+ db_index=True,
170
+ help_text="Related payment ID (if applicable)"
148
171
  )
149
172
 
150
- # Additional metadata
151
- reference_id = models.CharField(
152
- max_length=255,
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 = 'user_transactions'
164
- verbose_name = "Transaction"
165
- verbose_name_plural = "Transactions"
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
- ordering = ['-created_at']
174
-
200
+
175
201
  def __str__(self):
176
202
  sign = "+" if self.amount_usd >= 0 else ""
177
- return f"{self.user.email} - {sign}${self.amount_usd} ({self.get_transaction_type_display()})"
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
- def clean(self):
190
- """Validate transaction data."""
191
-
192
- # Validate balance calculation
193
- expected_balance = self.balance_before + self.amount_usd
194
- if abs(expected_balance - self.balance_after) > 0.01: # Allow for rounding
195
- raise ValidationError(
196
- f"Balance calculation error: {self.balance_before} + {self.amount_usd} != {self.balance_after}"
197
- )
198
-
199
- # Validate transaction type and amount sign
200
- if self.transaction_type == self.TransactionType.PAYMENT and self.amount_usd <= 0:
201
- raise ValidationError("Payment transactions must have positive amounts")
202
-
203
- if self.transaction_type == self.TransactionType.SUBSCRIPTION and self.amount_usd >= 0:
204
- raise ValidationError("Subscription transactions must have negative amounts")
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 run validation."""
208
- self.clean()
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)