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
@@ -0,0 +1,174 @@
1
+ """
2
+ Balance Signals for the Universal Payment System v2.0.
3
+
4
+ Minimal signals focused on cache invalidation and notifications.
5
+ Business logic stays in UserBalanceManager and TransactionManager.
6
+ """
7
+
8
+ from django.db.models.signals import post_save, post_delete, pre_save
9
+ from django.dispatch import receiver
10
+ from django.core.cache import cache
11
+ from django.utils import timezone
12
+
13
+ from ..models import UserBalance, Transaction
14
+ from django_cfg.modules.django_logger import get_logger
15
+
16
+ logger = get_logger("balance_signals")
17
+
18
+
19
+ @receiver(pre_save, sender=UserBalance)
20
+ def store_original_balance(sender, instance: UserBalance, **kwargs):
21
+ """Store original balance for change detection."""
22
+ if instance.pk:
23
+ try:
24
+ original = UserBalance.objects.get(pk=instance.pk)
25
+ instance._original_balance = original.balance_usd
26
+ except UserBalance.DoesNotExist:
27
+ instance._original_balance = None
28
+ else:
29
+ instance._original_balance = None
30
+
31
+
32
+ @receiver(post_save, sender=UserBalance)
33
+ def handle_balance_change(sender, instance: UserBalance, created: bool, **kwargs):
34
+ """
35
+ Handle balance changes - only cache clearing and notifications.
36
+
37
+ Business logic (analytics, calculations) stays in managers.
38
+ """
39
+ if created:
40
+ logger.info(f"New balance created", extra={
41
+ 'user_id': instance.user.id,
42
+ 'initial_balance': instance.balance_usd
43
+ })
44
+ else:
45
+ # Check if balance changed
46
+ if hasattr(instance, '_original_balance'):
47
+ old_balance = instance._original_balance or 0.0
48
+ new_balance = instance.balance_usd
49
+
50
+ if old_balance != new_balance:
51
+ balance_change = new_balance - old_balance
52
+
53
+ logger.info(f"Balance changed", extra={
54
+ 'user_id': instance.user.id,
55
+ 'old_balance': old_balance,
56
+ 'new_balance': new_balance,
57
+ 'change_amount': balance_change
58
+ })
59
+
60
+ # Check for low balance warning (notification only)
61
+ if new_balance < 10.0 and old_balance >= 10.0:
62
+ _trigger_low_balance_warning(instance)
63
+
64
+ # Check for zero balance (notification only)
65
+ if new_balance <= 0.0 and old_balance > 0.0:
66
+ _handle_zero_balance(instance)
67
+
68
+ # Clear balance-related caches
69
+ _clear_balance_caches(instance.user.id)
70
+
71
+
72
+ @receiver(post_save, sender=Transaction)
73
+ def handle_transaction_creation(sender, instance: Transaction, created: bool, **kwargs):
74
+ """
75
+ Handle transaction creation - only logging and cache clearing.
76
+
77
+ Business logic (analytics, balance updates) stays in managers.
78
+ """
79
+ if created:
80
+ logger.info(f"New transaction created", extra={
81
+ 'transaction_id': str(instance.id),
82
+ 'user_id': instance.user.id,
83
+ 'transaction_type': instance.transaction_type,
84
+ 'amount': instance.amount,
85
+ 'payment_id': str(instance.payment_id) if instance.payment_id else None
86
+ })
87
+
88
+ # Clear related caches
89
+ _clear_balance_caches(instance.user.id)
90
+
91
+
92
+ @receiver(post_delete, sender=Transaction)
93
+ def handle_transaction_deletion(sender, instance: Transaction, **kwargs):
94
+ """Handle transaction deletion (should be rare)."""
95
+ logger.warning(f"Transaction deleted", extra={
96
+ 'transaction_id': str(instance.id),
97
+ 'user_id': instance.user.id,
98
+ 'transaction_type': instance.transaction_type,
99
+ 'amount': instance.amount,
100
+ 'deletion_timestamp': timezone.now().isoformat()
101
+ })
102
+
103
+ # Clear caches
104
+ _clear_balance_caches(instance.user.id)
105
+
106
+
107
+ # Helper functions (notifications and caching only)
108
+
109
+ def _trigger_low_balance_warning(balance: UserBalance):
110
+ """Trigger low balance warning for user (notification only)."""
111
+ try:
112
+ logger.warning(f"Low balance warning", extra={
113
+ 'user_id': balance.user.id,
114
+ 'current_balance': balance.balance_usd,
115
+ 'threshold': 10.0
116
+ })
117
+
118
+ # Set warning flag in cache for frontend
119
+ cache.set(
120
+ f"low_balance_warning:{balance.user.id}",
121
+ {
122
+ 'balance': balance.balance_usd,
123
+ 'timestamp': timezone.now().isoformat(),
124
+ 'threshold': 10.0
125
+ },
126
+ timeout=86400 # 24 hours
127
+ )
128
+
129
+ except Exception as e:
130
+ logger.error(f"Failed to trigger low balance warning: {e}")
131
+
132
+
133
+ def _handle_zero_balance(balance: UserBalance):
134
+ """Handle zero balance situation (notification only)."""
135
+ try:
136
+ logger.warning(f"Zero balance reached", extra={
137
+ 'user_id': balance.user.id,
138
+ 'previous_balance': getattr(balance, '_original_balance', 'unknown')
139
+ })
140
+
141
+ # Set zero balance flag in cache
142
+ cache.set(
143
+ f"zero_balance:{balance.user.id}",
144
+ {
145
+ 'timestamp': timezone.now().isoformat(),
146
+ 'previous_balance': getattr(balance, '_original_balance', 0.0)
147
+ },
148
+ timeout=86400 * 7 # 7 days
149
+ )
150
+
151
+ except Exception as e:
152
+ logger.error(f"Failed to handle zero balance: {e}")
153
+
154
+
155
+ def _clear_balance_caches(user_id: int):
156
+ """Clear all balance-related cache entries for user."""
157
+ try:
158
+ cache_keys = [
159
+ f"user_balance:{user_id}",
160
+ f"user_transactions:{user_id}",
161
+ f"balance_summary:{user_id}",
162
+ f"balance_history:{user_id}",
163
+ f"transaction_stats:{user_id}",
164
+ ]
165
+
166
+ cache.delete_many(cache_keys)
167
+
168
+ logger.debug(f"Cleared balance caches", extra={
169
+ 'user_id': user_id,
170
+ 'cache_keys_cleared': len(cache_keys)
171
+ })
172
+
173
+ except Exception as e:
174
+ logger.warning(f"Failed to clear balance caches: {e}")
@@ -1,128 +1,159 @@
1
1
  """
2
- 🔄 Universal Payment Signals
2
+ Payment Signals for the Universal Payment System v2.0.
3
3
 
4
- Automatic payment processing and balance management via Django signals.
4
+ Minimal signals focused on cache invalidation and notifications.
5
+ Business logic stays in PaymentManager.
5
6
  """
6
7
 
7
8
  from django.db.models.signals import post_save, pre_save
8
9
  from django.dispatch import receiver
9
- from django.db import transaction
10
+ from django.core.cache import cache
10
11
  from django.utils import timezone
11
- import logging
12
12
 
13
- from ..models import UniversalPayment, UserBalance, Transaction
14
- from ..services.cache import SimpleCache
15
- from django_cfg.core.redis import RedisService
13
+ from ..models import UniversalPayment
14
+ from django_cfg.modules.django_logger import get_logger
16
15
 
17
- logger = logging.getLogger(__name__)
16
+ logger = get_logger("payment_signals")
18
17
 
19
18
 
20
19
  @receiver(pre_save, sender=UniversalPayment)
21
- def store_original_payment_status(sender, instance, **kwargs):
22
- """Store original payment status for change detection."""
20
+ def store_original_status(sender, instance: UniversalPayment, **kwargs):
21
+ """Store original status for change detection."""
23
22
  if instance.pk:
24
23
  try:
25
- old_instance = UniversalPayment.objects.get(pk=instance.pk)
26
- instance._original_status = old_instance.status
24
+ original = UniversalPayment.objects.get(pk=instance.pk)
25
+ instance._original_status = original.status
27
26
  except UniversalPayment.DoesNotExist:
28
27
  instance._original_status = None
28
+ else:
29
+ instance._original_status = None
29
30
 
30
31
 
31
32
  @receiver(post_save, sender=UniversalPayment)
32
- def process_payment_status_changes(sender, instance, created, **kwargs):
33
- """Process payment status changes and update user balance."""
34
- if created:
35
- logger.info(f"New payment created: {instance.internal_payment_id} for user {instance.user.email}")
36
- return
33
+ def handle_payment_changes(sender, instance: UniversalPayment, created: bool, **kwargs):
34
+ """
35
+ Handle payment changes - only cache clearing and notifications.
37
36
 
38
- # Check if status changed to completed
39
- if hasattr(instance, '_original_status'):
40
- old_status = instance._original_status
41
- new_status = instance.status
42
-
43
- if old_status != new_status:
44
- logger.info(
45
- f"Payment status changed: {instance.internal_payment_id} "
46
- f"for user {instance.user.email} - {old_status} → {new_status}"
47
- )
37
+ Business logic (balance updates, etc.) handled by managers.
38
+ """
39
+ if created:
40
+ logger.info(f"New payment created", extra={
41
+ 'payment_id': str(instance.id),
42
+ 'user_id': instance.user.id,
43
+ 'amount_usd': instance.amount_usd,
44
+ 'provider': instance.provider,
45
+ 'status': instance.status
46
+ })
47
+ else:
48
+ # Check for status changes
49
+ if hasattr(instance, '_original_status'):
50
+ old_status = instance._original_status
51
+ new_status = instance.status
48
52
 
49
- # Process completed payment
50
- if new_status == UniversalPayment.PaymentStatus.COMPLETED and old_status != new_status:
51
- _process_completed_payment(instance)
53
+ if old_status != new_status:
54
+ logger.info(f"Payment status changed", extra={
55
+ 'payment_id': str(instance.id),
56
+ 'user_id': instance.user.id,
57
+ 'old_status': old_status,
58
+ 'new_status': new_status
59
+ })
60
+
61
+ # Handle completed payment
62
+ if new_status == 'completed' and old_status != 'completed':
63
+ _handle_payment_completed(instance)
64
+
65
+ # Handle failed payment
66
+ elif new_status in ['failed', 'expired', 'cancelled'] and old_status not in ['failed', 'expired', 'cancelled']:
67
+ _handle_payment_failed(instance)
68
+
69
+ # Clear payment-related caches
70
+ _clear_payment_caches(instance)
52
71
 
53
72
 
54
- def _process_completed_payment(payment: UniversalPayment):
55
- """Process completed payment and add funds to user balance."""
73
+ def _handle_payment_completed(payment: UniversalPayment):
74
+ """
75
+ Handle completed payment - delegate to manager for business logic.
76
+ """
56
77
  try:
57
- with transaction.atomic():
58
- # Get or create user balance
59
- balance, created = UserBalance.objects.get_or_create(
60
- user=payment.user,
61
- defaults={
62
- 'amount_usd': 0,
63
- 'reserved_usd': 0
64
- }
65
- )
66
-
67
- # Add funds to balance
68
- old_balance = balance.amount_usd
69
- balance.amount_usd += payment.amount_usd
70
- balance.save()
71
-
72
- # Create transaction record
73
- Transaction.objects.create(
74
- user=payment.user,
75
- transaction_type=Transaction.TransactionType.PAYMENT,
76
- amount_usd=payment.amount_usd,
77
- balance_before=old_balance,
78
- balance_after=balance.amount_usd,
79
- description=f"Payment completed: {payment.internal_payment_id}",
80
- payment=payment,
81
- metadata={
82
- 'provider': payment.provider,
83
- 'provider_payment_id': payment.provider_payment_id,
84
- 'amount_usd': str(payment.amount_usd),
85
- 'currency_code': payment.currency_code
86
- }
87
- )
88
-
89
- # Mark payment as processed
90
- payment.processed_at = timezone.now()
91
- payment.save(update_fields=['processed_at'])
92
-
93
- # Clear Redis cache for user
94
- try:
95
- redis_service = RedisService()
96
- redis_service.invalidate_user_cache(payment.user.id)
97
- except Exception as e:
98
- logger.warning(f"Failed to clear Redis cache for user {payment.user.id}: {e}")
99
-
100
- logger.info(
101
- f"Payment {payment.internal_payment_id} processed successfully. "
102
- f"User {payment.user.email} balance: ${balance.amount_usd}"
103
- )
104
-
78
+ # Use manager method which has all the business logic
79
+ from ..models import UserBalance
80
+
81
+ transaction_record = UserBalance.objects.add_funds_to_user(
82
+ user=payment.user,
83
+ amount=payment.amount_usd,
84
+ transaction_type='payment',
85
+ description=f"Payment completed: {payment.id}",
86
+ payment_id=payment.id
87
+ )
88
+
89
+ # Mark payment as processed
90
+ payment.completed_at = timezone.now()
91
+ payment.save(update_fields=['completed_at', 'updated_at'])
92
+
93
+ logger.info(f"Payment completed and processed", extra={
94
+ 'payment_id': str(payment.id),
95
+ 'user_id': payment.user.id,
96
+ 'amount_usd': payment.amount_usd,
97
+ 'transaction_id': str(transaction_record.id)
98
+ })
99
+
105
100
  except Exception as e:
106
- logger.error(f"Error processing completed payment {payment.internal_payment_id}: {e}")
107
- raise
101
+ logger.error(f"Failed to process completed payment", extra={
102
+ 'payment_id': str(payment.id),
103
+ 'user_id': payment.user.id,
104
+ 'error': str(e)
105
+ })
108
106
 
109
107
 
110
- @receiver(post_save, sender=UniversalPayment)
111
- def log_payment_webhook_data(sender, instance, created, **kwargs):
112
- """Log webhook data for audit purposes."""
113
- if not created and instance.webhook_data:
114
- logger.info(
115
- f"Webhook data received for payment {instance.internal_payment_id}: "
116
- f"status={instance.status}, provider={instance.provider}"
108
+ def _handle_payment_failed(payment: UniversalPayment):
109
+ """
110
+ Handle failed payment - just logging and notifications.
111
+ """
112
+ try:
113
+ logger.warning(f"Payment failed", extra={
114
+ 'payment_id': str(payment.id),
115
+ 'user_id': payment.user.id,
116
+ 'amount_usd': payment.amount_usd,
117
+ 'status': payment.status,
118
+ 'provider': payment.provider
119
+ })
120
+
121
+ # Set failure notification in cache
122
+ cache.set(
123
+ f"payment_failed:{payment.user.id}:{payment.id}",
124
+ {
125
+ 'payment_id': str(payment.id),
126
+ 'amount_usd': payment.amount_usd,
127
+ 'status': payment.status,
128
+ 'timestamp': timezone.now().isoformat()
129
+ },
130
+ timeout=86400 * 7 # 7 days
117
131
  )
132
+
133
+ except Exception as e:
134
+ logger.error(f"Failed to handle payment failure", extra={
135
+ 'payment_id': str(payment.id),
136
+ 'error': str(e)
137
+ })
118
138
 
119
139
 
120
- @receiver(post_save, sender=Transaction)
121
- def log_transaction_creation(sender, instance, created, **kwargs):
122
- """Log transaction creation for audit trail."""
123
- if created:
124
- logger.info(
125
- f"New transaction: {instance.transaction_type} "
126
- f"${instance.amount_usd} for user {instance.user.email} "
127
- f"(balance: ${instance.balance_after})"
128
- )
140
+ def _clear_payment_caches(payment: UniversalPayment):
141
+ """Clear payment-related cache entries."""
142
+ try:
143
+ cache_keys = [
144
+ f"user_payments:{payment.user.id}",
145
+ f"payment_stats:{payment.user.id}",
146
+ f"payment_summary:{payment.user.id}",
147
+ f"provider_stats:{payment.provider}",
148
+ ]
149
+
150
+ cache.delete_many(cache_keys)
151
+
152
+ logger.debug(f"Cleared payment caches", extra={
153
+ 'payment_id': str(payment.id),
154
+ 'user_id': payment.user.id,
155
+ 'cache_keys_cleared': len(cache_keys)
156
+ })
157
+
158
+ except Exception as e:
159
+ logger.warning(f"Failed to clear payment caches: {e}")