django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) 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/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,146 +1,165 @@
1
1
  """
2
- Subscription models for the universal payments system.
2
+ Subscription models for the Universal Payment System v2.0.
3
+
4
+ Handles user subscriptions and API access control.
3
5
  """
4
6
 
5
7
  from django.db import models
6
8
  from django.contrib.auth import get_user_model
7
- from django.core.validators import MinValueValidator
9
+ from django.core.validators import MinValueValidator, MaxValueValidator
10
+ from django.core.exceptions import ValidationError
8
11
  from django.utils import timezone
9
12
  from datetime import timedelta
10
- from .base import UUIDTimestampedModel, TimestampedModel
13
+ from .base import UUIDTimestampedModel
11
14
 
12
15
  User = get_user_model()
13
16
 
14
17
 
15
- class EndpointGroup(TimestampedModel):
16
- """API endpoint groups for subscription management."""
18
+ class EndpointGroup(models.Model):
19
+ """
20
+ API endpoint group for subscription management.
21
+
22
+ Groups related API endpoints for subscription-based access control.
23
+ """
17
24
 
18
25
  name = models.CharField(
19
26
  max_length=100,
20
27
  unique=True,
21
- help_text="Endpoint group name"
28
+ help_text="Endpoint group name (e.g., 'Payment API', 'Balance API')"
22
29
  )
23
- display_name = models.CharField(
24
- max_length=200,
25
- help_text="Human-readable name"
30
+
31
+ code = models.CharField(
32
+ max_length=50,
33
+ unique=True,
34
+ help_text="Endpoint group code (e.g., 'payments', 'balance')"
26
35
  )
36
+
27
37
  description = models.TextField(
28
38
  blank=True,
29
- help_text="Group description"
39
+ help_text="Description of what this endpoint group provides"
30
40
  )
31
41
 
32
- # Pricing tiers
33
- basic_price = models.FloatField(
34
- default=0.0,
35
- validators=[MinValueValidator(0.0)],
36
- help_text="Basic tier monthly price"
37
- )
38
- premium_price = models.FloatField(
39
- default=0.0,
40
- validators=[MinValueValidator(0.0)],
41
- help_text="Premium tier monthly price"
42
- )
43
- enterprise_price = models.FloatField(
44
- default=0.0,
45
- validators=[MinValueValidator(0.0)],
46
- help_text="Enterprise tier monthly price"
47
- )
48
-
49
- # Usage limits per tier
50
- basic_limit = models.PositiveIntegerField(
51
- default=1000,
52
- help_text="Basic tier monthly usage limit"
53
- )
54
- premium_limit = models.PositiveIntegerField(
55
- default=10000,
56
- help_text="Premium tier monthly usage limit"
57
- )
58
- enterprise_limit = models.PositiveIntegerField(
59
- default=0, # 0 = unlimited
60
- help_text="Enterprise tier monthly usage limit (0 = unlimited)"
42
+ # Access control
43
+ is_enabled = models.BooleanField(
44
+ default=True,
45
+ help_text="Whether this endpoint group is available"
61
46
  )
62
47
 
63
- # Settings
64
- is_active = models.BooleanField(
48
+ requires_subscription = models.BooleanField(
65
49
  default=True,
66
- help_text="Is this endpoint group active"
50
+ help_text="Whether access requires an active subscription"
67
51
  )
68
- require_api_key = models.BooleanField(
69
- default=True,
70
- help_text="Require API key for access"
52
+
53
+ # Rate limiting defaults
54
+ default_rate_limit = models.PositiveIntegerField(
55
+ default=1000,
56
+ help_text="Default requests per hour for this endpoint group"
71
57
  )
72
58
 
73
- # Import and assign manager
74
- from ..managers import EndpointGroupManager
75
- objects = EndpointGroupManager()
59
+ # Timestamps
60
+ created_at = models.DateTimeField(auto_now_add=True)
61
+ updated_at = models.DateTimeField(auto_now=True)
76
62
 
77
63
  class Meta:
78
- db_table = 'endpoint_groups'
79
- verbose_name = "Endpoint Group"
80
- verbose_name_plural = "Endpoint Groups"
81
- indexes = [
82
- models.Index(fields=['name']),
83
- models.Index(fields=['is_active']),
84
- ]
64
+ db_table = 'payments_endpoint_groups'
65
+ verbose_name = 'Endpoint Group'
66
+ verbose_name_plural = 'Endpoint Groups'
85
67
  ordering = ['name']
86
68
 
87
69
  def __str__(self):
88
- return self.display_name
89
-
90
- def get_price_for_tier(self, tier: str) -> float:
91
- """Get price for specific tier."""
92
- tier_prices = {
93
- 'basic': self.basic_price,
94
- 'premium': self.premium_price,
95
- 'enterprise': self.enterprise_price,
96
- }
97
- return tier_prices.get(tier, 0.0)
98
-
99
- def get_limit_for_tier(self, tier: str) -> int:
100
- """Get usage limit for specific tier."""
101
- tier_limits = {
102
- 'basic': self.basic_limit,
103
- 'premium': self.premium_limit,
104
- 'enterprise': self.enterprise_limit,
105
- }
106
- return tier_limits.get(tier, 0)
70
+ return f"{self.name} ({self.code})"
71
+
72
+ def clean(self):
73
+ """Validate endpoint group data."""
74
+ if self.code:
75
+ self.code = self.code.lower().replace(' ', '_')
76
+
77
+
78
+ class SubscriptionQuerySet(models.QuerySet):
79
+ """Optimized queryset for subscription operations."""
80
+
81
+ def optimized(self):
82
+ """Prevent N+1 queries."""
83
+ return self.select_related('user').prefetch_related('endpoint_groups')
84
+
85
+ def active(self):
86
+ """Get active subscriptions."""
87
+ return self.filter(
88
+ status=Subscription.SubscriptionStatus.ACTIVE,
89
+ expires_at__gt=timezone.now()
90
+ )
91
+
92
+ def expired(self):
93
+ """Get expired subscriptions."""
94
+ return self.filter(expires_at__lte=timezone.now())
95
+
96
+ def by_tier(self, tier):
97
+ """Filter by subscription tier."""
98
+ return self.filter(tier=tier)
99
+
100
+ def by_user(self, user):
101
+ """Filter by user."""
102
+ return self.filter(user=user)
103
+
104
+
105
+ class SubscriptionManager(models.Manager):
106
+ """Manager for subscription operations."""
107
+
108
+ def get_queryset(self):
109
+ """Return optimized queryset by default."""
110
+ return SubscriptionQuerySet(self.model, using=self._db)
111
+
112
+ def optimized(self):
113
+ """Get optimized queryset."""
114
+ return self.get_queryset().optimized()
115
+
116
+ def active(self):
117
+ """Get active subscriptions."""
118
+ return self.get_queryset().active()
119
+
120
+ def expired(self):
121
+ """Get expired subscriptions."""
122
+ return self.get_queryset().expired()
123
+
124
+ def by_tier(self, tier):
125
+ """Get subscriptions by tier."""
126
+ return self.get_queryset().by_tier(tier)
107
127
 
108
128
 
109
129
  class Subscription(UUIDTimestampedModel):
110
- """User subscriptions to endpoint groups."""
130
+ """
131
+ User subscription model for API access control.
132
+
133
+ Manages user subscriptions with different tiers and access levels.
134
+ """
111
135
 
112
136
  class SubscriptionStatus(models.TextChoices):
113
137
  ACTIVE = "active", "Active"
114
138
  INACTIVE = "inactive", "Inactive"
115
- EXPIRED = "expired", "Expired"
116
- CANCELLED = "cancelled", "Cancelled"
117
139
  SUSPENDED = "suspended", "Suspended"
140
+ CANCELLED = "cancelled", "Cancelled"
141
+ EXPIRED = "expired", "Expired"
118
142
 
119
143
  class SubscriptionTier(models.TextChoices):
120
- BASIC = "basic", "Basic"
121
- PREMIUM = "premium", "Premium"
122
- ENTERPRISE = "enterprise", "Enterprise"
144
+ FREE = "free", "Free Tier"
145
+ BASIC = "basic", "Basic Tier"
146
+ PRO = "pro", "Pro Tier"
147
+ ENTERPRISE = "enterprise", "Enterprise Tier"
123
148
 
124
149
  user = models.ForeignKey(
125
150
  User,
126
151
  on_delete=models.CASCADE,
127
- related_name='subscriptions',
128
- help_text="Subscriber"
129
- )
130
- endpoint_group = models.ForeignKey(
131
- EndpointGroup,
132
- on_delete=models.CASCADE,
133
- related_name='subscriptions',
134
- help_text="Endpoint group"
152
+ related_name='payment_subscriptions',
153
+ help_text="User who owns this subscription"
135
154
  )
136
155
 
137
- # Subscription details
138
156
  tier = models.CharField(
139
157
  max_length=20,
140
158
  choices=SubscriptionTier.choices,
141
- default=SubscriptionTier.BASIC,
159
+ default=SubscriptionTier.FREE,
142
160
  help_text="Subscription tier"
143
161
  )
162
+
144
163
  status = models.CharField(
145
164
  max_length=20,
146
165
  choices=SubscriptionStatus.choices,
@@ -148,123 +167,184 @@ class Subscription(UUIDTimestampedModel):
148
167
  help_text="Subscription status"
149
168
  )
150
169
 
151
- # Pricing
152
- monthly_price = models.FloatField(
153
- validators=[MinValueValidator(0.0)],
154
- help_text="Monthly subscription price"
170
+ # Access control
171
+ endpoint_groups = models.ManyToManyField(
172
+ EndpointGroup,
173
+ related_name='subscriptions',
174
+ blank=True,
175
+ help_text="Endpoint groups accessible with this subscription"
155
176
  )
156
177
 
157
- # Usage tracking
158
- usage_limit = models.PositiveIntegerField(
178
+ # Rate limiting
179
+ requests_per_hour = models.PositiveIntegerField(
180
+ default=100,
181
+ validators=[MinValueValidator(1), MaxValueValidator(100000)],
182
+ help_text="API requests allowed per hour"
183
+ )
184
+
185
+ requests_per_day = models.PositiveIntegerField(
159
186
  default=1000,
160
- help_text="Monthly usage limit (0 = unlimited)"
187
+ validators=[MinValueValidator(1), MaxValueValidator(1000000)],
188
+ help_text="API requests allowed per day"
161
189
  )
162
- usage_current = models.PositiveIntegerField(
163
- default=0,
164
- help_text="Current month usage"
190
+
191
+ # Subscription period
192
+ starts_at = models.DateTimeField(
193
+ default=timezone.now,
194
+ help_text="When this subscription starts"
165
195
  )
166
196
 
167
- # Billing
168
- last_billed = models.DateTimeField(
169
- null=True,
170
- blank=True,
171
- help_text="Last billing date"
197
+ expires_at = models.DateTimeField(
198
+ help_text="When this subscription expires"
172
199
  )
173
- next_billing = models.DateTimeField(
174
- null=True,
175
- blank=True,
176
- help_text="Next billing date"
200
+
201
+ # Billing information
202
+ monthly_cost_usd = models.FloatField(
203
+ default=0.0,
204
+ validators=[MinValueValidator(0.0)],
205
+ help_text="Monthly cost in USD"
177
206
  )
178
207
 
179
- # Lifecycle
180
- expires_at = models.DateTimeField(
181
- null=True,
182
- blank=True,
183
- help_text="Subscription expiration"
208
+ # Usage tracking
209
+ total_requests = models.PositiveIntegerField(
210
+ default=0,
211
+ help_text="Total API requests made with this subscription"
184
212
  )
185
- cancelled_at = models.DateTimeField(
213
+
214
+ last_request_at = models.DateTimeField(
186
215
  null=True,
187
216
  blank=True,
188
- help_text="Cancellation date"
217
+ help_text="When the last API request was made"
189
218
  )
190
219
 
191
- # Metadata
192
- metadata = models.JSONField(
193
- default=dict,
194
- help_text="Additional subscription metadata"
220
+ # Auto-renewal
221
+ auto_renew = models.BooleanField(
222
+ default=False,
223
+ help_text="Whether to automatically renew this subscription"
195
224
  )
196
225
 
197
- # Import and assign manager
198
- from ..managers import SubscriptionManager
226
+ # Manager
227
+ from .managers.subscription_managers import SubscriptionManager
199
228
  objects = SubscriptionManager()
200
229
 
201
230
  class Meta:
202
- db_table = 'user_subscriptions'
203
- verbose_name = "Subscription"
204
- verbose_name_plural = "Subscriptions"
231
+ db_table = 'payments_subscriptions'
232
+ verbose_name = 'Subscription'
233
+ verbose_name_plural = 'Subscriptions'
234
+ ordering = ['-created_at']
205
235
  indexes = [
206
236
  models.Index(fields=['user', 'status']),
207
- models.Index(fields=['endpoint_group', 'status']),
208
237
  models.Index(fields=['status', 'expires_at']),
209
- models.Index(fields=['next_billing']),
210
- models.Index(fields=['created_at']),
238
+ models.Index(fields=['tier', 'status']),
239
+ ]
240
+ constraints = [
241
+ models.UniqueConstraint(
242
+ fields=['user'],
243
+ condition=models.Q(status='active'),
244
+ name='one_active_subscription_per_user'
245
+ ),
211
246
  ]
212
- unique_together = [['user', 'endpoint_group']] # One subscription per user per group
213
- ordering = ['-created_at']
214
247
 
215
248
  def __str__(self):
216
- return f"{self.user.email} - {self.endpoint_group.name} ({self.tier})"
249
+ return f"{self.user.username} - {self.tier} ({self.status})"
217
250
 
218
- def is_active(self) -> bool:
219
- """Check if subscription is currently active."""
220
- now = timezone.now()
251
+ def save(self, *args, **kwargs):
252
+ """Override save to set default expiration."""
253
+ if not self.expires_at:
254
+ # Default to 30 days from start
255
+ self.expires_at = self.starts_at + timedelta(days=30)
221
256
 
257
+ super().save(*args, **kwargs)
258
+
259
+ def clean(self):
260
+ """Validate subscription data."""
261
+ if self.expires_at and self.starts_at and self.expires_at <= self.starts_at:
262
+ raise ValidationError("Expiration date must be after start date")
263
+
264
+ if self.requests_per_day < self.requests_per_hour:
265
+ raise ValidationError("Daily limit cannot be less than hourly limit")
266
+
267
+ @property
268
+ def is_active(self) -> bool:
269
+ """Check if subscription is active and not expired."""
222
270
  return (
223
271
  self.status == self.SubscriptionStatus.ACTIVE and
224
- (self.expires_at is None or self.expires_at > now)
272
+ self.expires_at > timezone.now()
225
273
  )
226
274
 
227
- def is_usage_exceeded(self) -> bool:
228
- """Check if usage limit is exceeded."""
229
- return self.usage_limit > 0 and self.usage_current >= self.usage_limit
275
+ @property
276
+ def is_expired(self) -> bool:
277
+ """Check if subscription is expired."""
278
+ return timezone.now() > self.expires_at
230
279
 
231
- def get_usage_percentage(self) -> float:
232
- """Get usage as percentage (0-100)."""
233
- if self.usage_limit == 0:
234
- return 0.0 # Unlimited
235
-
236
- return min((self.usage_current / self.usage_limit) * 100, 100.0)
237
-
238
- def can_use_api(self) -> bool:
239
- """Check if user can use API (active and not exceeded)."""
240
- return self.is_active() and not self.is_usage_exceeded()
241
-
242
- def increment_usage(self, count: int = 1):
243
- """Increment usage counter."""
244
- self.usage_current += count
245
- self.save(update_fields=['usage_current'])
246
-
247
- def reset_usage(self):
248
- """Reset usage counter (for new billing period)."""
249
- self.usage_current = 0
250
- self.save(update_fields=['usage_current'])
251
-
252
- def cancel(self):
253
- """Cancel subscription."""
254
- self.status = self.SubscriptionStatus.CANCELLED
255
- self.cancelled_at = timezone.now()
256
- self.save(update_fields=['status', 'cancelled_at'])
257
-
258
- def extend_billing_period(self):
259
- """Extend billing period by one month."""
260
- if self.next_billing:
261
- self.next_billing += timedelta(days=30)
262
- else:
263
- self.next_billing = timezone.now() + timedelta(days=30)
264
-
265
- if self.expires_at:
266
- self.expires_at += timedelta(days=30)
267
- else:
268
- self.expires_at = timezone.now() + timedelta(days=30)
280
+ @property
281
+ def days_remaining(self) -> int:
282
+ """Get days remaining until expiration."""
283
+ if self.is_expired:
284
+ return 0
285
+ delta = self.expires_at - timezone.now()
286
+ return max(0, delta.days)
287
+
288
+ @property
289
+ def usage_percentage(self) -> float:
290
+ """Get usage percentage for current period."""
291
+ # This would need to be calculated based on actual usage tracking
292
+ # For now, return 0.0 as placeholder
293
+ return 0.0
294
+
295
+ @property
296
+ def tier_display(self) -> str:
297
+ """Get display name for tier."""
298
+ return self.get_tier_display()
299
+
300
+ @property
301
+ def status_color(self) -> str:
302
+ """Get color for status display."""
303
+ colors = {
304
+ self.SubscriptionStatus.ACTIVE: 'success',
305
+ self.SubscriptionStatus.INACTIVE: 'secondary',
306
+ self.SubscriptionStatus.SUSPENDED: 'warning',
307
+ self.SubscriptionStatus.CANCELLED: 'danger',
308
+ self.SubscriptionStatus.EXPIRED: 'danger',
309
+ }
310
+ return colors.get(self.status, 'secondary')
311
+
312
+ def activate(self):
313
+ """Activate subscription (delegates to manager)."""
314
+ return self.__class__.objects.activate_subscription(self)
315
+
316
+ def suspend(self, reason=None):
317
+ """Suspend subscription (delegates to manager)."""
318
+ return self.__class__.objects.suspend_subscription(self, reason)
319
+
320
+ def cancel(self, reason=None):
321
+ """Cancel subscription (delegates to manager)."""
322
+ return self.__class__.objects.cancel_subscription(self, reason)
323
+
324
+ def renew(self, duration_days: int = 30):
325
+ """Renew subscription (delegates to manager)."""
326
+ return self.__class__.objects.renew_subscription(self, duration_days)
327
+
328
+ def has_access_to_endpoint_group(self, endpoint_group_code: str) -> bool:
329
+ """Check if subscription has access to specific endpoint group."""
330
+ if not self.is_active:
331
+ return False
269
332
 
270
- self.save(update_fields=['next_billing', 'expires_at'])
333
+ return self.endpoint_groups.filter(
334
+ code=endpoint_group_code,
335
+ is_enabled=True
336
+ ).exists()
337
+
338
+ def increment_usage(self):
339
+ """Increment usage counter (delegates to manager)."""
340
+ return self.__class__.objects.increment_subscription_usage(self)
341
+
342
+ @classmethod
343
+ def get_active_for_user(cls, user: User) -> 'Subscription':
344
+ """Get active subscription for user (delegates to manager)."""
345
+ return cls.objects.get_active_for_user(user)
346
+
347
+ @classmethod
348
+ def create_free_subscription(cls, user: User) -> 'Subscription':
349
+ """Create free tier subscription for user (delegates to manager)."""
350
+ return cls.objects.create_free_subscription(user)