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
@@ -0,0 +1,599 @@
1
+ """
2
+ Balance and transaction managers for the Universal Payment System v2.0.
3
+
4
+ Optimized querysets and managers for balance and transaction operations.
5
+ """
6
+
7
+ from django.db import models, transaction
8
+ from django.utils import timezone
9
+ from django_cfg.modules.django_logger import get_logger
10
+
11
+ logger = get_logger("balance_managers")
12
+
13
+
14
+ class UserBalanceManager(models.Manager):
15
+ """
16
+ Manager for UserBalance operations.
17
+
18
+ Provides methods for balance management and atomic operations.
19
+ """
20
+
21
+ def get_or_create_for_user(self, user):
22
+ """
23
+ Get or create balance for user.
24
+
25
+ Args:
26
+ user: User instance
27
+
28
+ Returns:
29
+ UserBalance: Balance instance
30
+ """
31
+ balance, created = self.get_or_create(
32
+ user=user,
33
+ defaults={'balance_usd': 0.0}
34
+ )
35
+
36
+ if created:
37
+ logger.info(f"Created new balance for user", extra={
38
+ 'user_id': user.id,
39
+ 'initial_balance': 0.0
40
+ })
41
+
42
+ return balance
43
+
44
+ def add_funds_to_user(self, user, amount, transaction_type='deposit',
45
+ description=None, payment_id=None):
46
+ """
47
+ Add funds to user balance atomically (business logic in manager).
48
+
49
+ Args:
50
+ user: User instance
51
+ amount: Amount to add (positive)
52
+ transaction_type: Type of transaction
53
+ description: Transaction description
54
+ payment_id: Related payment ID
55
+
56
+ Returns:
57
+ Transaction: Created transaction record
58
+ """
59
+ if amount <= 0:
60
+ raise ValueError("Amount must be positive")
61
+
62
+ # Get or create balance
63
+ balance = self.get_or_create_for_user(user)
64
+
65
+ with transaction.atomic():
66
+ # Update balance
67
+ balance.balance_usd += amount
68
+ balance.total_deposited += amount
69
+ balance.last_transaction_at = timezone.now()
70
+ balance.save(update_fields=[
71
+ 'balance_usd', 'total_deposited', 'last_transaction_at', 'updated_at'
72
+ ])
73
+
74
+ # Create transaction record
75
+ from ..balance import Transaction
76
+ transaction_record = Transaction.objects.create(
77
+ user=user,
78
+ transaction_type=transaction_type,
79
+ amount_usd=amount,
80
+ balance_after=balance.balance_usd,
81
+ description=description or f"Added ${amount:.2f} to balance",
82
+ payment_id=payment_id
83
+ )
84
+
85
+ logger.info(f"Added funds to user balance", extra={
86
+ 'user_id': user.id,
87
+ 'amount': amount,
88
+ 'new_balance': balance.balance_usd,
89
+ 'transaction_id': str(transaction_record.id),
90
+ 'payment_id': payment_id
91
+ })
92
+
93
+ # Update analytics
94
+ self.update_balance_analytics(user, amount)
95
+
96
+ return transaction_record
97
+
98
+ def subtract_funds_from_user(self, user, amount, transaction_type='withdrawal',
99
+ description=None, payment_id=None):
100
+ """
101
+ Subtract funds from user balance atomically (business logic in manager).
102
+
103
+ Args:
104
+ user: User instance
105
+ amount: Amount to subtract (positive)
106
+ transaction_type: Type of transaction
107
+ description: Transaction description
108
+ payment_id: Related payment ID
109
+
110
+ Returns:
111
+ Transaction: Created transaction record
112
+ """
113
+ if amount <= 0:
114
+ raise ValueError("Amount must be positive")
115
+
116
+ # Get balance
117
+ try:
118
+ balance = self.get(user=user)
119
+ except self.model.DoesNotExist:
120
+ raise ValueError("User has no balance record")
121
+
122
+ if amount > balance.balance_usd:
123
+ raise ValueError(f"Insufficient balance: ${balance.balance_usd:.2f} < ${amount:.2f}")
124
+
125
+ with transaction.atomic():
126
+ # Update balance
127
+ balance.balance_usd -= amount
128
+ balance.total_spent += amount
129
+ balance.last_transaction_at = timezone.now()
130
+ balance.save(update_fields=[
131
+ 'balance_usd', 'total_spent', 'last_transaction_at', 'updated_at'
132
+ ])
133
+
134
+ # Create transaction record
135
+ from ..balance import Transaction
136
+ transaction_record = Transaction.objects.create(
137
+ user=user,
138
+ transaction_type=transaction_type,
139
+ amount_usd=-amount, # Negative for withdrawals
140
+ balance_after=balance.balance_usd,
141
+ description=description or f"Subtracted ${amount:.2f} from balance",
142
+ payment_id=payment_id
143
+ )
144
+
145
+ logger.info(f"Subtracted funds from user balance", extra={
146
+ 'user_id': user.id,
147
+ 'amount': amount,
148
+ 'new_balance': balance.balance_usd,
149
+ 'transaction_id': str(transaction_record.id),
150
+ 'payment_id': payment_id
151
+ })
152
+
153
+ # Update analytics
154
+ self.update_balance_analytics(user, -amount)
155
+
156
+ return transaction_record
157
+
158
+ def update_balance_analytics(self, user, balance_change):
159
+ """
160
+ Update balance analytics in cache (moved from signals).
161
+
162
+ Args:
163
+ user: User instance
164
+ balance_change: Amount of balance change
165
+ """
166
+ try:
167
+ from django.core.cache import cache
168
+
169
+ user_id = user.id
170
+
171
+ # Update balance history
172
+ history_key = f"balance_history:{user_id}"
173
+ history = cache.get(history_key, [])
174
+
175
+ history.append({
176
+ 'timestamp': timezone.now().isoformat(),
177
+ 'balance': self.get_or_create_for_user(user).balance_usd,
178
+ 'change': balance_change
179
+ })
180
+
181
+ # Keep only last 100 entries
182
+ if len(history) > 100:
183
+ history = history[-100:]
184
+
185
+ cache.set(history_key, history, timeout=86400 * 7) # 7 days
186
+
187
+ # Update daily totals
188
+ today = timezone.now().date().isoformat()
189
+ daily_key = f"balance_changes:{user_id}:{today}"
190
+
191
+ daily_data = cache.get(daily_key, {'total_change': 0.0, 'transaction_count': 0})
192
+ daily_data['total_change'] += balance_change
193
+ daily_data['transaction_count'] += 1
194
+
195
+ cache.set(daily_key, daily_data, timeout=86400 * 2) # 2 days
196
+
197
+ logger.debug(f"Updated balance analytics", extra={
198
+ 'user_id': user_id,
199
+ 'balance_change': balance_change,
200
+ 'total_change_today': daily_data['total_change']
201
+ })
202
+
203
+ except Exception as e:
204
+ logger.warning(f"Failed to update balance analytics", extra={
205
+ 'user_id': user.id,
206
+ 'error': str(e)
207
+ })
208
+
209
+
210
+ class TransactionQuerySet(models.QuerySet):
211
+ """
212
+ Optimized queryset for transaction operations.
213
+
214
+ Provides efficient queries for transaction history and analysis.
215
+ """
216
+
217
+ def optimized(self):
218
+ """Prevent N+1 queries with select_related."""
219
+ return self.select_related('user')
220
+
221
+ def by_user(self, user):
222
+ """Filter transactions by user."""
223
+ return self.filter(user=user)
224
+
225
+ def by_type(self, transaction_type):
226
+ """Filter by transaction type."""
227
+ return self.filter(transaction_type=transaction_type)
228
+
229
+ def by_payment(self, payment_id):
230
+ """Filter by related payment ID."""
231
+ return self.filter(payment_id=payment_id)
232
+
233
+ # Transaction type filters
234
+ def deposits(self):
235
+ """Get deposit transactions (positive amounts)."""
236
+ return self.filter(transaction_type='deposit', amount_usd__gt=0)
237
+
238
+ def withdrawals(self):
239
+ """Get withdrawal transactions (negative amounts)."""
240
+ return self.filter(transaction_type='withdrawal', amount_usd__lt=0)
241
+
242
+ def payments(self):
243
+ """Get payment-related transactions."""
244
+ return self.filter(transaction_type='payment')
245
+
246
+ def refunds(self):
247
+ """Get refund transactions."""
248
+ return self.filter(transaction_type='refund')
249
+
250
+ def fees(self):
251
+ """Get fee transactions."""
252
+ return self.filter(transaction_type='fee')
253
+
254
+ def bonuses(self):
255
+ """Get bonus transactions."""
256
+ return self.filter(transaction_type='bonus')
257
+
258
+ def adjustments(self):
259
+ """Get adjustment transactions."""
260
+ return self.filter(transaction_type='adjustment')
261
+
262
+ # Amount-based filters
263
+ def credits(self):
264
+ """Get credit transactions (positive amounts)."""
265
+ return self.filter(amount_usd__gt=0)
266
+
267
+ def debits(self):
268
+ """Get debit transactions (negative amounts)."""
269
+ return self.filter(amount_usd__lt=0)
270
+
271
+ def large_amounts(self, threshold=100.0):
272
+ """
273
+ Get transactions above threshold amount.
274
+
275
+ Args:
276
+ threshold: USD amount threshold (default: $100)
277
+ """
278
+ return self.filter(amount_usd__gte=threshold)
279
+
280
+ def small_amounts(self, threshold=10.0):
281
+ """
282
+ Get transactions below threshold amount.
283
+
284
+ Args:
285
+ threshold: USD amount threshold (default: $10)
286
+ """
287
+ return self.filter(amount_usd__lte=threshold)
288
+
289
+ # Time-based filters
290
+ def recent(self, hours=24):
291
+ """
292
+ Get transactions from last N hours.
293
+
294
+ Args:
295
+ hours: Number of hours to look back (default: 24)
296
+ """
297
+ since = timezone.now() - timezone.timedelta(hours=hours)
298
+ return self.filter(created_at__gte=since)
299
+
300
+ def today(self):
301
+ """Get transactions created today."""
302
+ today = timezone.now().date()
303
+ return self.filter(created_at__date=today)
304
+
305
+ def this_week(self):
306
+ """Get transactions from this week."""
307
+ week_start = timezone.now().date() - timezone.timedelta(days=timezone.now().weekday())
308
+ return self.filter(created_at__date__gte=week_start)
309
+
310
+ def this_month(self):
311
+ """Get transactions from this month."""
312
+ month_start = timezone.now().replace(day=1).date()
313
+ return self.filter(created_at__date__gte=month_start)
314
+
315
+ def date_range(self, start_date, end_date):
316
+ """
317
+ Get transactions within date range.
318
+
319
+ Args:
320
+ start_date: Start date (inclusive)
321
+ end_date: End date (inclusive)
322
+ """
323
+ return self.filter(created_at__date__range=[start_date, end_date])
324
+
325
+ # Aggregation methods
326
+ def total_amount(self):
327
+ """Get total amount for queryset."""
328
+ result = self.aggregate(total=models.Sum('amount_usd'))
329
+ return result['total'] or 0.0
330
+
331
+ def total_credits(self):
332
+ """Get total credit amount."""
333
+ result = self.credits().aggregate(total=models.Sum('amount_usd'))
334
+ return result['total'] or 0.0
335
+
336
+ def total_debits(self):
337
+ """Get total debit amount (absolute value)."""
338
+ result = self.debits().aggregate(total=models.Sum('amount_usd'))
339
+ return abs(result['total'] or 0.0)
340
+
341
+ def average_amount(self):
342
+ """Get average transaction amount."""
343
+ result = self.aggregate(avg=models.Avg('amount_usd'))
344
+ return result['avg'] or 0.0
345
+
346
+ def count_by_type(self):
347
+ """Get count of transactions grouped by type."""
348
+ return self.values('transaction_type').annotate(
349
+ count=models.Count('id'),
350
+ total_amount=models.Sum('amount_usd')
351
+ ).order_by('transaction_type')
352
+
353
+ def daily_summary(self, days=30):
354
+ """
355
+ Get daily transaction summary for the last N days.
356
+
357
+ Args:
358
+ days: Number of days to analyze (default: 30)
359
+ """
360
+ since = timezone.now().date() - timezone.timedelta(days=days)
361
+ return self.filter(created_at__date__gte=since).extra(
362
+ select={'day': 'date(created_at)'}
363
+ ).values('day').annotate(
364
+ count=models.Count('id'),
365
+ total_amount=models.Sum('amount_usd'),
366
+ credits=models.Sum('amount_usd', filter=models.Q(amount_usd__gt=0)),
367
+ debits=models.Sum('amount_usd', filter=models.Q(amount_usd__lt=0))
368
+ ).order_by('day')
369
+
370
+
371
+ class TransactionManager(models.Manager):
372
+ """
373
+ Manager for transaction operations with optimized queries.
374
+
375
+ Provides high-level methods for transaction analysis and reporting.
376
+ """
377
+
378
+ def get_queryset(self):
379
+ """Return optimized queryset by default."""
380
+ return TransactionQuerySet(self.model, using=self._db)
381
+
382
+ def optimized(self):
383
+ """Get optimized queryset."""
384
+ return self.get_queryset().optimized()
385
+
386
+ # User-based methods
387
+ def by_user(self, user):
388
+ """Get transactions by user."""
389
+ return self.get_queryset().by_user(user)
390
+
391
+ def by_type(self, transaction_type):
392
+ """Get transactions by type."""
393
+ return self.get_queryset().by_type(transaction_type)
394
+
395
+ # Transaction type methods
396
+ def deposits(self):
397
+ """Get deposit transactions."""
398
+ return self.get_queryset().deposits()
399
+
400
+ def withdrawals(self):
401
+ """Get withdrawal transactions."""
402
+ return self.get_queryset().withdrawals()
403
+
404
+ def payments(self):
405
+ """Get payment transactions."""
406
+ return self.get_queryset().payments()
407
+
408
+ def refunds(self):
409
+ """Get refund transactions."""
410
+ return self.get_queryset().refunds()
411
+
412
+ # Time-based methods
413
+ def recent(self, hours=24):
414
+ """Get recent transactions."""
415
+ return self.get_queryset().recent(hours)
416
+
417
+ def today(self):
418
+ """Get today's transactions."""
419
+ return self.get_queryset().today()
420
+
421
+ def this_week(self):
422
+ """Get this week's transactions."""
423
+ return self.get_queryset().this_week()
424
+
425
+ def this_month(self):
426
+ """Get this month's transactions."""
427
+ return self.get_queryset().this_month()
428
+
429
+ # Analysis methods
430
+ def get_user_balance_history(self, user, days=30):
431
+ """
432
+ Get balance history for a user over the last N days.
433
+
434
+ Args:
435
+ user: User instance
436
+ days: Number of days to analyze (default: 30)
437
+
438
+ Returns:
439
+ list: Daily balance snapshots
440
+ """
441
+ transactions = self.by_user(user).filter(
442
+ created_at__gte=timezone.now() - timezone.timedelta(days=days)
443
+ ).order_by('created_at')
444
+
445
+ history = []
446
+ current_balance = 0.0
447
+
448
+ for transaction in transactions:
449
+ current_balance = transaction.balance_after
450
+ history.append({
451
+ 'date': transaction.created_at.date(),
452
+ 'balance': current_balance,
453
+ 'transaction_id': str(transaction.id),
454
+ 'transaction_type': transaction.transaction_type,
455
+ 'amount': transaction.amount_usd
456
+ })
457
+
458
+ return history
459
+
460
+ def get_transaction_stats(self, user=None, days=30):
461
+ """
462
+ Get transaction statistics.
463
+
464
+ Args:
465
+ user: User instance (optional, for user-specific stats)
466
+ days: Number of days to analyze (default: 30)
467
+
468
+ Returns:
469
+ dict: Transaction statistics
470
+ """
471
+ queryset = self.get_queryset()
472
+ if user:
473
+ queryset = queryset.by_user(user)
474
+
475
+ since = timezone.now() - timezone.timedelta(days=days)
476
+ queryset = queryset.filter(created_at__gte=since)
477
+
478
+ stats = {
479
+ 'total_transactions': queryset.count(),
480
+ 'total_amount': queryset.total_amount(),
481
+ 'total_credits': queryset.total_credits(),
482
+ 'total_debits': queryset.total_debits(),
483
+ 'average_amount': queryset.average_amount(),
484
+ 'by_type': list(queryset.count_by_type()),
485
+ 'deposits_count': queryset.deposits().count(),
486
+ 'withdrawals_count': queryset.withdrawals().count(),
487
+ 'payments_count': queryset.payments().count(),
488
+ 'refunds_count': queryset.refunds().count(),
489
+ }
490
+
491
+ logger.info(f"Generated transaction stats for {days} days", extra={
492
+ 'user_id': user.id if user else None,
493
+ 'days': days,
494
+ 'total_transactions': stats['total_transactions'],
495
+ 'total_amount': stats['total_amount']
496
+ })
497
+
498
+ return stats
499
+
500
+ def get_daily_summary(self, days=30):
501
+ """
502
+ Get daily transaction summary.
503
+
504
+ Args:
505
+ days: Number of days to analyze (default: 30)
506
+
507
+ Returns:
508
+ QuerySet: Daily summary data
509
+ """
510
+ return self.get_queryset().daily_summary(days)
511
+
512
+ def create_deposit(self, user, amount, description=None, payment_id=None, metadata=None):
513
+ """
514
+ Create a deposit transaction.
515
+
516
+ Args:
517
+ user: User instance
518
+ amount: Deposit amount (positive)
519
+ description: Transaction description
520
+ payment_id: Related payment ID
521
+ metadata: Additional metadata
522
+
523
+ Returns:
524
+ Transaction: Created transaction
525
+ """
526
+ if amount <= 0:
527
+ raise ValueError("Deposit amount must be positive")
528
+
529
+ # Get or create user balance
530
+ from ..balance import UserBalance
531
+ balance = UserBalance.get_or_create_for_user(user)
532
+
533
+ # Create transaction via balance method (ensures atomicity)
534
+ transaction = balance.add_funds(
535
+ amount=amount,
536
+ transaction_type='deposit',
537
+ description=description or f"Deposit of ${amount:.2f}",
538
+ payment_id=payment_id
539
+ )
540
+
541
+ # Add metadata if provided
542
+ if metadata:
543
+ transaction.metadata = metadata
544
+ transaction.save(update_fields=['metadata'])
545
+
546
+ logger.info(f"Created deposit transaction", extra={
547
+ 'user_id': user.id,
548
+ 'amount': amount,
549
+ 'transaction_id': str(transaction.id),
550
+ 'payment_id': payment_id
551
+ })
552
+
553
+ return transaction
554
+
555
+ def create_withdrawal(self, user, amount, description=None, payment_id=None, metadata=None):
556
+ """
557
+ Create a withdrawal transaction.
558
+
559
+ Args:
560
+ user: User instance
561
+ amount: Withdrawal amount (positive, will be made negative)
562
+ description: Transaction description
563
+ payment_id: Related payment ID
564
+ metadata: Additional metadata
565
+
566
+ Returns:
567
+ Transaction: Created transaction
568
+ """
569
+ if amount <= 0:
570
+ raise ValueError("Withdrawal amount must be positive")
571
+
572
+ # Get user balance
573
+ from ..balance import UserBalance
574
+ try:
575
+ balance = UserBalance.objects.get(user=user)
576
+ except UserBalance.DoesNotExist:
577
+ raise ValueError("User has no balance record")
578
+
579
+ # Create transaction via balance method (ensures atomicity)
580
+ transaction = balance.subtract_funds(
581
+ amount=amount,
582
+ transaction_type='withdrawal',
583
+ description=description or f"Withdrawal of ${amount:.2f}",
584
+ payment_id=payment_id
585
+ )
586
+
587
+ # Add metadata if provided
588
+ if metadata:
589
+ transaction.metadata = metadata
590
+ transaction.save(update_fields=['metadata'])
591
+
592
+ logger.info(f"Created withdrawal transaction", extra={
593
+ 'user_id': user.id,
594
+ 'amount': amount,
595
+ 'transaction_id': str(transaction.id),
596
+ 'payment_id': payment_id
597
+ })
598
+
599
+ return transaction