django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,15 @@
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
- from .currencies import Currency, CurrencyNetwork
12
+ from .currencies import Currency, Network, ProviderCurrency
12
13
 
13
14
  # Payment models
14
15
  from .payments import UniversalPayment
@@ -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,43 +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
- 'CurrencyNetwork',
43
+ 'Network',
44
+ 'ProviderCurrency',
47
45
 
48
- # Models
46
+ # Core Models
49
47
  'UniversalPayment',
50
48
  'UserBalance',
51
49
  'Transaction',
52
- 'EndpointGroup',
53
50
  'Subscription',
51
+ 'EndpointGroup',
54
52
  'Tariff',
55
53
  'TariffEndpointGroup',
56
54
  'APIKey',
57
- 'PaymentEvent',
58
55
 
59
56
  # TextChoices
60
- 'CurrencyType',
61
57
  'PaymentStatus',
62
- 'PaymentProvider',
58
+ 'PaymentProvider',
59
+ 'CurrencyType',
63
60
  'TransactionType',
64
61
  'SubscriptionStatus',
65
62
  'SubscriptionTier',
66
- 'EventType',
67
- ]
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)