django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -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,134 +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
- from django_cfg.modules.django_logger import get_logger
12
12
 
13
- from ..models import UniversalPayment, UserBalance, Transaction
14
- from ..services.cache import SimpleCache
15
- from django.core.cache import cache
13
+ from ..models import UniversalPayment
14
+ from django_cfg.modules.django_logger import get_logger
16
15
 
17
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
- # Invalidate user cache using Django cache
96
- user_cache_pattern = f"payments:user:{payment.user.id}:*"
97
- # Note: Django cache doesn't support pattern deletion, so we clear specific keys
98
- cache.delete_many([
99
- f"payments:user:{payment.user.id}:balance",
100
- f"payments:user:{payment.user.id}:api_keys",
101
- f"payments:user:{payment.user.id}:subscriptions"
102
- ])
103
- except Exception as e:
104
- logger.warning(f"Failed to clear Redis cache for user {payment.user.id}: {e}")
105
-
106
- logger.info(
107
- f"Payment {payment.internal_payment_id} processed successfully. "
108
- f"User {payment.user.email} balance: ${balance.amount_usd}"
109
- )
110
-
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
+
111
100
  except Exception as e:
112
- logger.error(f"Error processing completed payment {payment.internal_payment_id}: {e}")
113
- 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
+ })
114
106
 
115
107
 
116
- @receiver(post_save, sender=UniversalPayment)
117
- def log_payment_webhook_data(sender, instance, created, **kwargs):
118
- """Log webhook data for audit purposes."""
119
- if not created and instance.webhook_data:
120
- logger.info(
121
- f"Webhook data received for payment {instance.internal_payment_id}: "
122
- 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
123
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
+ })
124
138
 
125
139
 
126
- @receiver(post_save, sender=Transaction)
127
- def log_transaction_creation(sender, instance, created, **kwargs):
128
- """Log transaction creation for audit trail."""
129
- if created:
130
- logger.info(
131
- f"New transaction: {instance.transaction_type} "
132
- f"${instance.amount_usd} for user {instance.user.email} "
133
- f"(balance: ${instance.balance_after})"
134
- )
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}")