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,232 @@
1
+ """
2
+ Webhook types for the Universal Payment System v2.0.
3
+
4
+ Pydantic models for webhook validation and processing.
5
+ """
6
+
7
+ from decimal import Decimal
8
+ from typing import Optional, Dict, Any, Literal
9
+ from pydantic import BaseModel, Field, ConfigDict, field_validator
10
+ from datetime import datetime, timedelta
11
+
12
+
13
+ class WebhookData(BaseModel):
14
+ """Base webhook data structure."""
15
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
16
+
17
+ provider: str = Field(description="Provider name")
18
+ payment_id: str = Field(description="Payment ID")
19
+ status: str = Field(description="Payment status")
20
+ timestamp: datetime = Field(description="Webhook timestamp")
21
+ signature: Optional[str] = Field(None, description="Webhook signature")
22
+ raw_payload: Dict[str, Any] = Field(description="Raw webhook payload")
23
+
24
+
25
+ class NowPaymentsWebhook(BaseModel):
26
+ """
27
+ NowPayments webhook structure.
28
+
29
+ Based on NowPayments IPN (Instant Payment Notification) format.
30
+ """
31
+ model_config = ConfigDict(validate_assignment=True, extra="allow")
32
+
33
+ # Required fields from NowPayments
34
+ payment_id: str = Field(description="NowPayments payment ID")
35
+ payment_status: Literal[
36
+ 'waiting', 'confirming', 'confirmed', 'sending', 'partially_paid',
37
+ 'finished', 'failed', 'refunded', 'expired'
38
+ ] = Field(description="Payment status")
39
+ pay_address: str = Field(description="Payment address")
40
+ price_amount: Decimal = Field(description="Original price amount")
41
+ price_currency: str = Field(description="Original price currency (USD)")
42
+ pay_amount: Decimal = Field(description="Amount to pay in crypto")
43
+ pay_currency: str = Field(description="Cryptocurrency code")
44
+ order_id: Optional[str] = Field(None, description="Order ID")
45
+ order_description: Optional[str] = Field(None, description="Order description")
46
+
47
+ # Optional fields
48
+ actually_paid: Optional[Decimal] = Field(None, description="Actually paid amount")
49
+ outcome_amount: Optional[Decimal] = Field(None, description="Outcome amount")
50
+ outcome_currency: Optional[str] = Field(None, description="Outcome currency")
51
+
52
+ # Network information
53
+ network: Optional[str] = Field(None, description="Blockchain network")
54
+ txn_id: Optional[str] = Field(None, description="Transaction ID")
55
+
56
+ # Timestamps
57
+ created_at: Optional[datetime] = Field(None, description="Creation timestamp")
58
+ updated_at: Optional[datetime] = Field(None, description="Update timestamp")
59
+
60
+ # Additional data
61
+ purchase_id: Optional[str] = Field(None, description="Purchase ID")
62
+ smart_contract: Optional[str] = Field(None, description="Smart contract address")
63
+ burning_percent: Optional[str] = Field(None, description="Burning percentage")
64
+
65
+ @field_validator('payment_status')
66
+ @classmethod
67
+ def validate_status(cls, v: str) -> str:
68
+ """Validate payment status."""
69
+ valid_statuses = [
70
+ 'waiting', 'confirming', 'confirmed', 'sending', 'partially_paid',
71
+ 'finished', 'failed', 'refunded', 'expired'
72
+ ]
73
+ if v not in valid_statuses:
74
+ raise ValueError(f"Invalid payment status: {v}")
75
+ return v
76
+
77
+ @field_validator('price_currency')
78
+ @classmethod
79
+ def validate_price_currency(cls, v: str) -> str:
80
+ """Validate price currency is USD."""
81
+ if v.upper() != 'USD':
82
+ raise ValueError("Price currency must be USD")
83
+ return v.upper()
84
+
85
+ def to_universal_status(self) -> str:
86
+ """Convert NowPayments status to universal payment status."""
87
+ status_mapping = {
88
+ 'waiting': 'pending',
89
+ 'confirming': 'confirming',
90
+ 'confirmed': 'confirmed',
91
+ 'sending': 'processing',
92
+ 'partially_paid': 'partial',
93
+ 'finished': 'completed',
94
+ 'failed': 'failed',
95
+ 'refunded': 'refunded',
96
+ 'expired': 'expired'
97
+ }
98
+ return status_mapping.get(self.payment_status, 'unknown')
99
+
100
+ def is_final_status(self) -> bool:
101
+ """Check if payment status is final."""
102
+ final_statuses = ['finished', 'failed', 'refunded', 'expired']
103
+ return self.payment_status in final_statuses
104
+
105
+ def is_successful(self) -> bool:
106
+ """Check if payment is successful."""
107
+ return self.payment_status == 'finished'
108
+
109
+
110
+ class WebhookProcessingResult(BaseModel):
111
+ """Result of webhook processing."""
112
+ model_config = ConfigDict(validate_assignment=True)
113
+
114
+ success: bool = Field(description="Processing success")
115
+ webhook_id: Optional[str] = Field(None, description="Webhook ID")
116
+ payment_id: Optional[str] = Field(None, description="Related payment ID")
117
+ provider: str = Field(description="Provider name")
118
+ status_before: Optional[str] = Field(None, description="Status before processing")
119
+ status_after: Optional[str] = Field(None, description="Status after processing")
120
+ actions_taken: list[str] = Field(default_factory=list, description="Actions performed")
121
+ balance_updated: bool = Field(default=False, description="Whether balance was updated")
122
+ notifications_sent: list[str] = Field(default_factory=list, description="Notifications sent")
123
+ error_message: Optional[str] = Field(None, description="Error message if failed")
124
+ processing_time_ms: Optional[int] = Field(None, description="Processing time in milliseconds")
125
+ processed: bool = Field(default=False, description="Whether webhook was processed")
126
+ timestamp: datetime = Field(default_factory=datetime.utcnow, description="Processing timestamp")
127
+
128
+
129
+ class WebhookValidationResult(BaseModel):
130
+ """Result of webhook validation."""
131
+ model_config = ConfigDict(validate_assignment=True)
132
+
133
+ is_valid: bool = Field(description="Validation result")
134
+ provider: str = Field(description="Provider name")
135
+ signature_valid: Optional[bool] = Field(None, description="Signature validation result")
136
+ payload_valid: bool = Field(description="Payload validation result")
137
+ error_message: Optional[str] = Field(None, description="Validation error message")
138
+ parsed_data: Optional[Dict[str, Any]] = Field(None, description="Parsed webhook data")
139
+
140
+
141
+ class WebhookSignature(BaseModel):
142
+ """Webhook signature validation data."""
143
+ model_config = ConfigDict(validate_assignment=True)
144
+
145
+ provider: str = Field(description="Provider name")
146
+ signature: str = Field(description="Webhook signature")
147
+ payload: str = Field(description="Raw payload string")
148
+ secret_key: str = Field(description="Secret key for validation")
149
+ algorithm: str = Field(default="sha512", description="Signature algorithm")
150
+
151
+ def validate_signature(self) -> bool:
152
+ """Validate webhook signature."""
153
+ import hmac
154
+ import hashlib
155
+
156
+ if self.algorithm == "sha512":
157
+ expected = hmac.new(
158
+ self.secret_key.encode('utf-8'),
159
+ self.payload.encode('utf-8'),
160
+ hashlib.sha512
161
+ ).hexdigest()
162
+ elif self.algorithm == "sha256":
163
+ expected = hmac.new(
164
+ self.secret_key.encode('utf-8'),
165
+ self.payload.encode('utf-8'),
166
+ hashlib.sha256
167
+ ).hexdigest()
168
+ else:
169
+ raise ValueError(f"Unsupported algorithm: {self.algorithm}")
170
+
171
+ return hmac.compare_digest(expected, self.signature)
172
+
173
+
174
+ class WebhookRetry(BaseModel):
175
+ """Webhook retry configuration."""
176
+ model_config = ConfigDict(validate_assignment=True)
177
+
178
+ webhook_id: str = Field(description="Webhook ID")
179
+ attempt_number: int = Field(description="Retry attempt number")
180
+ max_attempts: int = Field(default=3, description="Maximum retry attempts")
181
+ delay_seconds: int = Field(default=60, description="Delay between retries")
182
+ last_error: Optional[str] = Field(None, description="Last error message")
183
+ next_retry_at: Optional[datetime] = Field(None, description="Next retry timestamp")
184
+
185
+ def should_retry(self) -> bool:
186
+ """Check if webhook should be retried."""
187
+ return self.attempt_number < self.max_attempts
188
+
189
+ def calculate_next_retry(self) -> datetime:
190
+ """Calculate next retry timestamp with exponential backoff."""
191
+ import math
192
+
193
+ # Exponential backoff: delay * (2 ^ attempt_number)
194
+ delay = self.delay_seconds * (2 ** self.attempt_number)
195
+ # Cap at 1 hour
196
+ delay = min(delay, 3600)
197
+
198
+ return datetime.utcnow() + timedelta(seconds=delay)
199
+
200
+
201
+ class WebhookEvent(BaseModel):
202
+ """Webhook event for logging and monitoring."""
203
+ model_config = ConfigDict(validate_assignment=True)
204
+
205
+ event_id: str = Field(description="Event ID")
206
+ webhook_id: str = Field(description="Webhook ID")
207
+ provider: str = Field(description="Provider name")
208
+ event_type: Literal['received', 'validated', 'processed', 'failed', 'retried'] = Field(
209
+ description="Event type"
210
+ )
211
+ payment_id: Optional[str] = Field(None, description="Related payment ID")
212
+ status: Optional[str] = Field(None, description="Payment status")
213
+ message: Optional[str] = Field(None, description="Event message")
214
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
215
+ timestamp: datetime = Field(default_factory=datetime.utcnow, description="Event timestamp")
216
+ ip_address: Optional[str] = Field(None, description="Source IP address")
217
+ user_agent: Optional[str] = Field(None, description="User agent")
218
+
219
+ def to_log_entry(self) -> Dict[str, Any]:
220
+ """Convert to structured log entry."""
221
+ return {
222
+ 'event_id': self.event_id,
223
+ 'webhook_id': self.webhook_id,
224
+ 'provider': self.provider,
225
+ 'event_type': self.event_type,
226
+ 'payment_id': self.payment_id,
227
+ 'status': self.status,
228
+ 'message': self.message,
229
+ 'timestamp': self.timestamp.isoformat(),
230
+ 'ip_address': self.ip_address,
231
+ 'metadata': self.metadata
232
+ }
@@ -1,13 +1,38 @@
1
1
  """
2
- Universal Payment Signals.
2
+ Optimized Signals for the Universal Payment System v2.0.
3
3
 
4
- Automatically imports all signal handlers when the payments app is loaded.
4
+ Minimal signals that only handle:
5
+ - Cache invalidation
6
+ - Event notifications
7
+ - Audit logging
8
+
9
+ All business logic is in managers to avoid duplication.
5
10
  """
6
11
 
7
- from .api_key_signals import * # noqa: F401,F403
8
- from .payment_signals import * # noqa: F401,F403
9
- from .subscription_signals import * # noqa: F401,F403
12
+ from django.apps import apps
13
+ from django_cfg.modules.django_logger import get_logger
14
+
15
+ logger = get_logger("payment_signals")
16
+
17
+
18
+ def register_signals():
19
+ """
20
+ Register all payment system signals.
21
+
22
+ Called from apps.py when Django starts.
23
+ """
24
+ try:
25
+ # Import signal modules to register them
26
+ from . import payment_signals
27
+ from . import balance_signals
28
+ from . import subscription_signals
29
+ from . import api_key_signals
30
+
31
+ logger.info("Payment signals registered successfully")
32
+
33
+ except ImportError as e:
34
+ logger.error(f"Failed to register payment signals: {e}")
35
+
10
36
 
11
- __all__ = [
12
- # Signal functions are automatically exported by Django
13
- ]
37
+ # Auto-register signals when module is imported
38
+ register_signals()
@@ -1,160 +1,241 @@
1
1
  """
2
- 🔄 Universal API Keys Auto-Creation Signals
2
+ API Key Signals for the Universal Payment System v2.0.
3
3
 
4
- Automatic API key creation and management via Django signals.
5
- Enhanced version of CarAPI signals with universal support.
4
+ Minimal signals focused on cache invalidation and security notifications.
5
+ Business logic stays in APIKeyManager.
6
6
  """
7
7
 
8
8
  from django.db.models.signals import post_save, post_delete, pre_save
9
9
  from django.dispatch import receiver
10
- from django.contrib.auth import get_user_model
11
- from django.db import transaction
10
+ from django.core.cache import cache
12
11
  from django.utils import timezone
13
- from django_cfg.modules.django_logger import get_logger
14
12
 
15
13
  from ..models import APIKey
14
+ from django_cfg.modules.django_logger import get_logger
16
15
 
17
- User = get_user_model()
18
16
  logger = get_logger("api_key_signals")
19
17
 
20
18
 
21
- @receiver(post_save, sender=User)
22
- def create_default_api_key(sender, instance, created, **kwargs):
23
- """
24
- Automatically create default API key for new users.
25
- This ensures every user can immediately start using the API.
26
- """
27
- if created:
28
- try:
29
- with transaction.atomic():
30
- import secrets
31
- key_value = f"ak_{secrets.token_urlsafe(32)}"
32
-
33
- api_key = APIKey.objects.create(
34
- user=instance,
35
- name="Default API Key",
36
- key_value=key_value,
37
- key_prefix=key_value[:8],
38
- is_active=True
39
- )
40
-
41
- logger.info(
42
- f"Created default API key for user {instance.email}: {api_key.key_prefix}***"
43
- )
44
-
45
- # Optional: Send welcome email with API key info
46
- # This would be handled in custom project implementations
47
- # from .tasks import send_api_key_welcome_email
48
- # send_api_key_welcome_email.delay(instance.id, api_key.id)
49
-
50
- except Exception as e:
51
- logger.error(f"Failed to create default API key for user {instance.email}: {e}")
52
-
53
-
54
- @receiver(post_save, sender=User)
55
- def ensure_user_has_api_key(sender, instance, **kwargs):
56
- """
57
- Ensure user always has at least one API key.
58
- Creates one if user has no active keys.
59
- """
60
- # Skip if this is a new user (handled by create_default_api_key)
61
- if kwargs.get('created', False):
62
- return
63
-
64
- # Check if user has any active keys
65
- if not APIKey.objects.filter(user=instance, is_active=True).exists():
66
- try:
67
- with transaction.atomic():
68
- import secrets
69
- key_value = f"ak_{secrets.token_urlsafe(32)}"
70
-
71
- api_key = APIKey.objects.create(
72
- user=instance,
73
- name="Recovery API Key",
74
- key_value=key_value,
75
- key_prefix=key_value[:8],
76
- is_active=True
77
- )
78
- logger.info(
79
- f"Created recovery API key for user {instance.email}: {api_key.key_prefix}***"
80
- )
81
- except Exception as e:
82
- logger.error(f"Failed to create recovery API key for user {instance.email}: {e}")
83
-
84
-
85
19
  @receiver(pre_save, sender=APIKey)
86
- def store_original_status(sender, instance, **kwargs):
87
- """Store original status for change detection."""
20
+ def store_original_api_key_data(sender, instance: APIKey, **kwargs):
21
+ """Store original API key data for change detection."""
88
22
  if instance.pk:
89
23
  try:
90
- old_instance = APIKey.objects.get(pk=instance.pk)
91
- instance._original_is_active = old_instance.is_active
24
+ original = APIKey.objects.get(pk=instance.pk)
25
+ instance._original_is_active = original.is_active
26
+ instance._original_total_requests = original.total_requests
92
27
  except APIKey.DoesNotExist:
93
28
  instance._original_is_active = None
29
+ instance._original_total_requests = None
30
+ else:
31
+ instance._original_is_active = None
32
+ instance._original_total_requests = None
94
33
 
95
34
 
96
35
  @receiver(post_save, sender=APIKey)
97
- def log_api_key_changes(sender, instance, created, **kwargs):
98
- """Log API key creation and status changes for security monitoring."""
36
+ def handle_api_key_changes(sender, instance: APIKey, created: bool, **kwargs):
37
+ """
38
+ Handle API key changes - only cache clearing and security notifications.
39
+
40
+ Business logic (usage tracking, validation) stays in managers.
41
+ """
99
42
  if created:
100
- logger.info(
101
- f"New API key created: {instance.name} ({instance.key_prefix}***) "
102
- f"for user {instance.user.email}"
43
+ logger.info(f"New API key created", extra={
44
+ 'api_key_id': str(instance.id),
45
+ 'user_id': instance.user.id,
46
+ 'name': instance.name,
47
+ 'expires_at': instance.expires_at.isoformat() if instance.expires_at else None
48
+ })
49
+
50
+ # Set creation notification in cache
51
+ cache.set(
52
+ f"api_key_created:{instance.user.id}:{instance.id}",
53
+ {
54
+ 'api_key_id': str(instance.id),
55
+ 'name': instance.name,
56
+ 'timestamp': timezone.now().isoformat()
57
+ },
58
+ timeout=86400 # 24 hours
103
59
  )
60
+
104
61
  else:
105
- # Check if status changed
62
+ # Check for status changes
106
63
  if hasattr(instance, '_original_is_active'):
107
- old_status = instance._original_is_active
108
- new_status = instance.is_active
64
+ old_active = instance._original_is_active
65
+ new_active = instance.is_active
109
66
 
110
- if old_status is not None and old_status != new_status:
111
- status_text = "activated" if new_status else "deactivated"
112
- logger.warning(
113
- f"API key {status_text}: {instance.name} ({instance.key_prefix}***) "
114
- f"for user {instance.user.email}"
115
- )
116
-
117
-
118
- @receiver(post_save, sender=APIKey)
119
- def update_last_used_on_activation(sender, instance, created, **kwargs):
120
- """Update last_used when API key is activated."""
121
- if not created and instance.is_active and hasattr(instance, '_original_is_active'):
122
- if instance._original_is_active is False and instance.is_active is True:
123
- # Key was just activated
124
- APIKey.objects.filter(pk=instance.pk).update(
125
- last_used=timezone.now()
126
- )
67
+ if old_active != new_active:
68
+ if new_active:
69
+ _handle_api_key_activated(instance)
70
+ else:
71
+ _handle_api_key_deactivated(instance)
72
+
73
+ # Check for usage increases (security monitoring)
74
+ if hasattr(instance, '_original_total_requests'):
75
+ old_requests = instance._original_total_requests or 0
76
+ new_requests = instance.total_requests
77
+
78
+ if new_requests > old_requests:
79
+ request_increase = new_requests - old_requests
80
+
81
+ # Log high usage increases (potential security concern)
82
+ if request_increase > 100: # More than 100 requests at once
83
+ logger.warning(f"High API key usage increase", extra={
84
+ 'api_key_id': str(instance.id),
85
+ 'user_id': instance.user.id,
86
+ 'old_requests': old_requests,
87
+ 'new_requests': new_requests,
88
+ 'increase': request_increase
89
+ })
90
+
91
+ _handle_high_usage_alert(instance, request_increase)
92
+
93
+ # Clear API key-related caches
94
+ _clear_api_key_caches(instance)
127
95
 
128
96
 
129
97
  @receiver(post_delete, sender=APIKey)
130
- def log_api_key_deletion(sender, instance, **kwargs):
131
- """Log API key deletions for security audit."""
132
- logger.warning(
133
- f"API key deleted: {instance.name} ({instance.key_prefix}***) "
134
- f"for user {instance.user.email} - Status was: {'active' if instance.is_active else 'inactive'}"
98
+ def handle_api_key_deletion(sender, instance: APIKey, **kwargs):
99
+ """Handle API key deletion."""
100
+ logger.warning(f"API key deleted", extra={
101
+ 'api_key_id': str(instance.id),
102
+ 'user_id': instance.user.id,
103
+ 'name': instance.name,
104
+ 'total_requests': instance.total_requests,
105
+ 'deletion_timestamp': timezone.now().isoformat()
106
+ })
107
+
108
+ # Set deletion notification in cache
109
+ cache.set(
110
+ f"api_key_deleted:{instance.user.id}:{instance.id}",
111
+ {
112
+ 'api_key_id': str(instance.id),
113
+ 'name': instance.name,
114
+ 'total_requests': instance.total_requests,
115
+ 'timestamp': timezone.now().isoformat()
116
+ },
117
+ timeout=86400 * 30 # 30 days for audit
135
118
  )
136
-
137
-
138
- @receiver(post_delete, sender=APIKey)
139
- def ensure_user_has_remaining_key(sender, instance, **kwargs):
140
- """
141
- Ensure user still has at least one API key after deletion.
142
- Creates a new one if this was the last active key.
143
- """
144
- user = instance.user
145
119
 
146
- # Check if user has any remaining active keys
147
- if not APIKey.objects.filter(user=user, is_active=True).exists():
148
- try:
149
- with transaction.atomic():
150
- api_key = APIKey.objects.create(
151
- user=user,
152
- name="Auto Recovery API Key",
153
- is_active=True
154
- )
155
- logger.info(
156
- f"Created auto-recovery API key for user {user.email}: {api_key.key_prefix}*** "
157
- f"(previous key was deleted)"
158
- )
159
- except Exception as e:
160
- logger.error(f"Failed to create auto-recovery API key for user {user.email}: {e}")
120
+ # Clear caches
121
+ _clear_api_key_caches(instance)
122
+
123
+
124
+ # Helper functions (notifications and security monitoring only)
125
+
126
+ def _handle_api_key_activated(api_key: APIKey):
127
+ """Handle API key activation (notification only)."""
128
+ try:
129
+ logger.info(f"API key activated", extra={
130
+ 'api_key_id': str(api_key.id),
131
+ 'user_id': api_key.user.id,
132
+ 'name': api_key.name
133
+ })
134
+
135
+ # Set activation notification in cache
136
+ cache.set(
137
+ f"api_key_activated:{api_key.user.id}:{api_key.id}",
138
+ {
139
+ 'api_key_id': str(api_key.id),
140
+ 'name': api_key.name,
141
+ 'timestamp': timezone.now().isoformat()
142
+ },
143
+ timeout=86400 # 24 hours
144
+ )
145
+
146
+ except Exception as e:
147
+ logger.error(f"Failed to handle API key activation: {e}")
148
+
149
+
150
+ def _handle_api_key_deactivated(api_key: APIKey):
151
+ """Handle API key deactivation (security notification)."""
152
+ try:
153
+ logger.warning(f"API key deactivated", extra={
154
+ 'api_key_id': str(api_key.id),
155
+ 'user_id': api_key.user.id,
156
+ 'name': api_key.name,
157
+ 'total_requests': api_key.total_requests
158
+ })
159
+
160
+ # Set deactivation notification in cache
161
+ cache.set(
162
+ f"api_key_deactivated:{api_key.user.id}:{api_key.id}",
163
+ {
164
+ 'api_key_id': str(api_key.id),
165
+ 'name': api_key.name,
166
+ 'total_requests': api_key.total_requests,
167
+ 'timestamp': timezone.now().isoformat()
168
+ },
169
+ timeout=86400 * 7 # 7 days
170
+ )
171
+
172
+ except Exception as e:
173
+ logger.error(f"Failed to handle API key deactivation: {e}")
174
+
175
+
176
+ def _handle_high_usage_alert(api_key: APIKey, request_increase: int):
177
+ """Handle high usage alert (security monitoring)."""
178
+ try:
179
+ logger.warning(f"High API key usage detected", extra={
180
+ 'api_key_id': str(api_key.id),
181
+ 'user_id': api_key.user.id,
182
+ 'request_increase': request_increase,
183
+ 'total_requests': api_key.total_requests
184
+ })
185
+
186
+ # Set high usage alert in cache
187
+ cache.set(
188
+ f"high_usage_alert:{api_key.user.id}:{api_key.id}",
189
+ {
190
+ 'api_key_id': str(api_key.id),
191
+ 'request_increase': request_increase,
192
+ 'total_requests': api_key.total_requests,
193
+ 'timestamp': timezone.now().isoformat()
194
+ },
195
+ timeout=86400 # 24 hours
196
+ )
197
+
198
+ # Check if we should temporarily disable the key (security measure)
199
+ if request_increase > 1000: # More than 1000 requests at once
200
+ logger.critical(f"Extremely high API usage - potential abuse", extra={
201
+ 'api_key_id': str(api_key.id),
202
+ 'user_id': api_key.user.id,
203
+ 'request_increase': request_increase
204
+ })
205
+
206
+ # Set critical alert flag
207
+ cache.set(
208
+ f"critical_usage_alert:{api_key.user.id}:{api_key.id}",
209
+ {
210
+ 'api_key_id': str(api_key.id),
211
+ 'request_increase': request_increase,
212
+ 'timestamp': timezone.now().isoformat(),
213
+ 'action_required': True
214
+ },
215
+ timeout=86400 * 7 # 7 days
216
+ )
217
+
218
+ except Exception as e:
219
+ logger.error(f"Failed to handle high usage alert: {e}")
220
+
221
+
222
+ def _clear_api_key_caches(api_key: APIKey):
223
+ """Clear API key-related cache entries."""
224
+ try:
225
+ cache_keys = [
226
+ f"api_key_validation:{api_key.key[:10]}...", # Partial key for security
227
+ f"user_api_keys:{api_key.user.id}",
228
+ f"api_key_stats:{api_key.user.id}",
229
+ f"api_key_usage:{api_key.id}",
230
+ ]
231
+
232
+ cache.delete_many(cache_keys)
233
+
234
+ logger.debug(f"Cleared API key caches", extra={
235
+ 'api_key_id': str(api_key.id),
236
+ 'user_id': api_key.user.id,
237
+ 'cache_keys_cleared': len(cache_keys)
238
+ })
239
+
240
+ except Exception as e:
241
+ logger.warning(f"Failed to clear API key caches: {e}")