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
@@ -1,228 +0,0 @@
1
- """
2
- Utilities for middleware processing.
3
- """
4
-
5
- from typing import Optional, List
6
- from django.http import HttpRequest
7
- from django.conf import settings
8
-
9
-
10
- def get_client_ip(request: HttpRequest) -> Optional[str]:
11
- """
12
- Get client IP address from request.
13
- Handles various proxy headers and configurations.
14
- """
15
-
16
- # Check for forwarded headers first
17
- forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
18
- if forwarded_for:
19
- # Take first IP in chain (original client)
20
- return forwarded_for.split(',')[0].strip()
21
-
22
- # Check for real IP header (common with nginx)
23
- real_ip = request.META.get('HTTP_X_REAL_IP')
24
- if real_ip:
25
- return real_ip.strip()
26
-
27
- # Check for Cloudflare header
28
- cf_ip = request.META.get('HTTP_CF_CONNECTING_IP')
29
- if cf_ip:
30
- return cf_ip.strip()
31
-
32
- # Fallback to remote address
33
- remote_addr = request.META.get('REMOTE_ADDR')
34
- if remote_addr:
35
- return remote_addr.strip()
36
-
37
- return None
38
-
39
-
40
- def is_api_request(request: HttpRequest, api_prefixes: Optional[List[str]] = None) -> bool:
41
- """
42
- Check if request is an API request based on path prefixes.
43
-
44
- Args:
45
- request: Django HTTP request
46
- api_prefixes: List of API prefixes to check (defaults to settings)
47
- """
48
-
49
- if api_prefixes is None:
50
- api_prefixes = getattr(settings, 'PAYMENTS_API_PREFIXES', ['/api/'])
51
-
52
- path = request.path
53
- return any(path.startswith(prefix) for prefix in api_prefixes)
54
-
55
-
56
- def extract_api_key(request: HttpRequest) -> Optional[str]:
57
- """
58
- Extract API key from request headers or query parameters.
59
- Supports multiple authentication methods.
60
-
61
- Priority:
62
- 1. Authorization header (Bearer token)
63
- 2. X-API-Key header
64
- 3. Query parameter (less secure, for testing)
65
- """
66
-
67
- # Method 1: Authorization header (Bearer token)
68
- auth_header = request.META.get('HTTP_AUTHORIZATION', '')
69
- if auth_header.startswith('Bearer '):
70
- return auth_header[7:] # Remove 'Bearer ' prefix
71
-
72
- # Method 2: X-API-Key header
73
- api_key_header = request.META.get('HTTP_X_API_KEY')
74
- if api_key_header:
75
- return api_key_header.strip()
76
-
77
- # Method 3: Custom header variations
78
- custom_headers = [
79
- 'HTTP_X_API_TOKEN',
80
- 'HTTP_APIKEY',
81
- 'HTTP_API_TOKEN',
82
- ]
83
-
84
- for header in custom_headers:
85
- value = request.META.get(header)
86
- if value:
87
- return value.strip()
88
-
89
- # Method 4: Query parameter (less secure, mainly for testing)
90
- if getattr(settings, 'PAYMENTS_ALLOW_API_KEY_IN_QUERY', False):
91
- query_key = request.GET.get('api_key') or request.GET.get('apikey')
92
- if query_key:
93
- return query_key.strip()
94
-
95
- return None
96
-
97
-
98
- def is_exempt_path(request: HttpRequest, exempt_paths: Optional[List[str]] = None) -> bool:
99
- """
100
- Check if request path is exempt from API key requirements.
101
-
102
- Args:
103
- request: Django HTTP request
104
- exempt_paths: List of exempt path prefixes (defaults to settings)
105
- """
106
-
107
- if exempt_paths is None:
108
- exempt_paths = getattr(settings, 'PAYMENTS_EXEMPT_PATHS', [
109
- '/admin/',
110
- '/cfg/',
111
- '/api/v1/api-key/validate/',
112
- ])
113
-
114
- path = request.path
115
- return any(path.startswith(exempt) for exempt in exempt_paths)
116
-
117
-
118
- def get_request_metadata(request: HttpRequest) -> dict:
119
- """
120
- Extract useful metadata from request for logging and analytics.
121
- """
122
-
123
- return {
124
- 'method': request.method,
125
- 'path': request.path,
126
- 'query_string': request.META.get('QUERY_STRING', ''),
127
- 'user_agent': request.META.get('HTTP_USER_AGENT', ''),
128
- 'referer': request.META.get('HTTP_REFERER', ''),
129
- 'ip_address': get_client_ip(request),
130
- 'content_type': request.META.get('CONTENT_TYPE', ''),
131
- 'content_length': request.META.get('CONTENT_LENGTH', 0),
132
- 'host': request.META.get('HTTP_HOST', ''),
133
- 'scheme': request.scheme,
134
- 'is_secure': request.is_secure(),
135
- }
136
-
137
-
138
- def should_track_request_body(request: HttpRequest, max_size: int = 10000) -> bool:
139
- """
140
- Determine if request body should be tracked for analytics.
141
-
142
- Args:
143
- request: Django HTTP request
144
- max_size: Maximum body size to track (bytes)
145
- """
146
-
147
- # Check content length
148
- content_length = request.META.get('CONTENT_LENGTH')
149
- if content_length and int(content_length) > max_size:
150
- return False
151
-
152
- # Don't track file uploads
153
- content_type = request.META.get('CONTENT_TYPE', '')
154
- if 'multipart/form-data' in content_type:
155
- return False
156
-
157
- # Don't track binary content
158
- if 'application/octet-stream' in content_type:
159
- return False
160
-
161
- # Don't track sensitive endpoints
162
- sensitive_paths = getattr(settings, 'PAYMENTS_SENSITIVE_PATHS', [
163
- '/api/v1/api-key/',
164
- '/api/v1/payment/',
165
- '/api/v1/subscription/',
166
- ])
167
-
168
- path = request.path
169
- if any(path.startswith(sensitive) for sensitive in sensitive_paths):
170
- return False
171
-
172
- return True
173
-
174
-
175
- def should_track_response_body(response, max_size: int = 10000) -> bool:
176
- """
177
- Determine if response body should be tracked for analytics.
178
-
179
- Args:
180
- response: Django HTTP response
181
- max_size: Maximum body size to track (bytes)
182
- """
183
-
184
- # Don't track large responses
185
- if hasattr(response, 'content') and len(response.content) > max_size:
186
- return False
187
-
188
- # Only track successful JSON responses
189
- if not (200 <= response.status_code < 300):
190
- return False
191
-
192
- # Check content type
193
- content_type = response.get('Content-Type', '')
194
- if 'application/json' not in content_type:
195
- return False
196
-
197
- return True
198
-
199
-
200
- def format_error_response(error_code: str,
201
- message: str,
202
- status_code: int = 400,
203
- additional_data: Optional[dict] = None) -> dict:
204
- """
205
- Format standardized error response for middleware.
206
-
207
- Args:
208
- error_code: Machine-readable error code
209
- message: Human-readable error message
210
- status_code: HTTP status code
211
- additional_data: Additional error data
212
- """
213
-
214
- from django.utils import timezone
215
-
216
- error_response = {
217
- 'error': {
218
- 'code': error_code,
219
- 'message': message,
220
- 'status_code': status_code,
221
- 'timestamp': timezone.now().isoformat(),
222
- }
223
- }
224
-
225
- if additional_data:
226
- error_response['error'].update(additional_data)
227
-
228
- return error_response
@@ -1,94 +0,0 @@
1
- """
2
- Validation utilities for payments module.
3
-
4
- Basic validation functions for API keys and subscription access.
5
- """
6
-
7
- import logging
8
- from typing import Optional, Dict, Any
9
- from django.contrib.auth import get_user_model
10
- from django.utils import timezone
11
-
12
- from ..models import APIKey, Subscription
13
-
14
- User = get_user_model()
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- def validate_api_key(api_key: str) -> bool:
19
- """
20
- Validate API key.
21
-
22
- Args:
23
- api_key: API key to validate
24
-
25
- Returns:
26
- True if valid, False otherwise
27
- """
28
- try:
29
- key = APIKey.objects.select_related('user').get(
30
- key_value=api_key,
31
- is_active=True
32
- )
33
-
34
- # Check if key is expired
35
- if key.expires_at and key.expires_at < timezone.now():
36
- return False
37
-
38
- return True
39
-
40
- except APIKey.DoesNotExist:
41
- return False
42
- except Exception as e:
43
- logger.error(f"Error validating API key: {e}")
44
- return False
45
-
46
-
47
- def check_subscription_access(user_id: int, endpoint_group: str) -> Dict[str, Any]:
48
- """
49
- Check subscription access for user and endpoint group.
50
-
51
- Args:
52
- user_id: User ID
53
- endpoint_group: Endpoint group name
54
-
55
- Returns:
56
- Access check result dictionary
57
- """
58
- try:
59
- subscription = Subscription.objects.select_related('endpoint_group').get(
60
- user_id=user_id,
61
- endpoint_group__name=endpoint_group,
62
- status='active',
63
- expires_at__gt=timezone.now()
64
- )
65
-
66
- # Check usage limits
67
- usage_percentage = (subscription.current_usage / subscription.monthly_limit) * 100
68
- remaining_requests = subscription.monthly_limit - subscription.current_usage
69
-
70
- return {
71
- 'allowed': remaining_requests > 0,
72
- 'subscription_id': str(subscription.id),
73
- 'remaining_requests': remaining_requests,
74
- 'usage_percentage': usage_percentage,
75
- 'reason': 'Active subscription' if remaining_requests > 0 else 'Usage limit exceeded'
76
- }
77
-
78
- except Subscription.DoesNotExist:
79
- return {
80
- 'allowed': False,
81
- 'reason': 'No active subscription found',
82
- 'subscription_id': None,
83
- 'remaining_requests': 0,
84
- 'usage_percentage': 0
85
- }
86
- except Exception as e:
87
- logger.error(f"Error checking subscription access: {e}")
88
- return {
89
- 'allowed': False,
90
- 'reason': f'Access check failed: {str(e)}',
91
- 'subscription_id': None,
92
- 'remaining_requests': 0,
93
- 'usage_percentage': 0
94
- }
@@ -1,62 +0,0 @@
1
- """
2
- DRF ViewSets for universal payments.
3
- """
4
-
5
- from .balance_views import UserBalanceViewSet, TransactionViewSet
6
- from .payment_views import (
7
- UserPaymentViewSet, UniversalPaymentViewSet,
8
- PaymentCreateView, PaymentStatusView
9
- )
10
- from .subscription_views import (
11
- UserSubscriptionViewSet, SubscriptionViewSet, EndpointGroupViewSet,
12
- SubscriptionCreateView, ActiveSubscriptionsView
13
- )
14
- from .api_key_views import (
15
- UserAPIKeyViewSet, APIKeyViewSet,
16
- APIKeyCreateView, APIKeyValidateView
17
- )
18
- from .currency_views import (
19
- CurrencyViewSet, CurrencyNetworkViewSet,
20
- SupportedCurrenciesView, CurrencyRatesView
21
- )
22
- from .tariff_views import (
23
- TariffViewSet, TariffEndpointGroupViewSet,
24
- AvailableTariffsView, TariffComparisonView
25
- )
26
-
27
- __all__ = [
28
- # Balance ViewSets
29
- 'UserBalanceViewSet',
30
- 'TransactionViewSet',
31
-
32
- # Payment ViewSets & Generics
33
- 'UserPaymentViewSet',
34
- 'UniversalPaymentViewSet',
35
- 'PaymentCreateView',
36
- 'PaymentStatusView',
37
-
38
- # Subscription ViewSets & Generics
39
- 'UserSubscriptionViewSet',
40
- 'SubscriptionViewSet',
41
- 'EndpointGroupViewSet',
42
- 'SubscriptionCreateView',
43
- 'ActiveSubscriptionsView',
44
-
45
- # API Key ViewSets & Generics
46
- 'UserAPIKeyViewSet',
47
- 'APIKeyViewSet',
48
- 'APIKeyCreateView',
49
- 'APIKeyValidateView',
50
-
51
- # Currency ViewSets & Generics
52
- 'CurrencyViewSet',
53
- 'CurrencyNetworkViewSet',
54
- 'SupportedCurrenciesView',
55
- 'CurrencyRatesView',
56
-
57
- # Tariff ViewSets & Generics
58
- 'TariffViewSet',
59
- 'TariffEndpointGroupViewSet',
60
- 'AvailableTariffsView',
61
- 'TariffComparisonView',
62
- ]
@@ -1,164 +0,0 @@
1
- """
2
- API Key ViewSets with nested routing.
3
- """
4
-
5
- from rest_framework import viewsets, permissions, status, generics
6
- from rest_framework.decorators import action
7
- from rest_framework.response import Response
8
- from django_filters.rest_framework import DjangoFilterBackend
9
- from django.contrib.auth import get_user_model
10
- from ..models import APIKey
11
- from ..serializers import (
12
- APIKeySerializer, APIKeyCreateSerializer, APIKeyListSerializer
13
- )
14
-
15
- User = get_user_model()
16
-
17
-
18
- class UserAPIKeyViewSet(viewsets.ModelViewSet):
19
- """Nested ViewSet for user API keys: /users/{user_id}/api-keys/"""
20
-
21
- serializer_class = APIKeySerializer
22
- permission_classes = [permissions.IsAuthenticated]
23
- filter_backends = [DjangoFilterBackend]
24
- filterset_fields = ['is_active']
25
-
26
- def get_queryset(self):
27
- """Filter by user from URL."""
28
- user_id = self.kwargs.get('user_pk')
29
- return APIKey.objects.filter(user_id=user_id).order_by('-created_at')
30
-
31
- def get_serializer_class(self):
32
- """Use different serializers for different actions."""
33
- if self.action == 'create':
34
- return APIKeyCreateSerializer
35
- elif self.action == 'list':
36
- return APIKeyListSerializer
37
- return APIKeySerializer
38
-
39
- def perform_create(self, serializer):
40
- """Set user from URL when creating."""
41
- user_id = self.kwargs.get('user_pk')
42
- user = User.objects.get(id=user_id)
43
-
44
- # Generate unique API key
45
- import secrets
46
- key_value = f"ak_{secrets.token_urlsafe(32)}"
47
-
48
- serializer.save(user=user, key_value=key_value)
49
-
50
- @action(detail=True, methods=['post'])
51
- def regenerate(self, request, user_pk=None, pk=None):
52
- """Regenerate API key."""
53
- api_key = self.get_object()
54
-
55
- # Generate new key
56
- import secrets
57
- api_key.key_value = f"ak_{secrets.token_urlsafe(32)}"
58
- api_key.usage_count = 0 # Reset usage
59
- api_key.save()
60
-
61
- serializer = self.get_serializer(api_key)
62
- return Response(serializer.data)
63
-
64
- @action(detail=True, methods=['post'])
65
- def deactivate(self, request, user_pk=None, pk=None):
66
- """Deactivate API key."""
67
- api_key = self.get_object()
68
- api_key.is_active = False
69
- api_key.save()
70
-
71
- return Response({'message': 'API key deactivated'})
72
-
73
- @action(detail=True, methods=['get'])
74
- def usage_stats(self, request, user_pk=None, pk=None):
75
- """Get usage statistics for API key."""
76
- api_key = self.get_object()
77
-
78
- return Response({
79
- 'usage_count': api_key.usage_count,
80
- 'last_used': api_key.last_used,
81
- 'is_valid': api_key.is_valid(),
82
- 'expires_at': api_key.expires_at,
83
- 'is_active': api_key.is_active,
84
- })
85
-
86
-
87
- class APIKeyViewSet(viewsets.ReadOnlyModelViewSet):
88
- """Global API keys ViewSet: /api-keys/"""
89
-
90
- queryset = APIKey.objects.all()
91
- serializer_class = APIKeySerializer
92
- permission_classes = [permissions.IsAuthenticated]
93
- filter_backends = [DjangoFilterBackend]
94
- filterset_fields = ['is_active']
95
-
96
- def get_queryset(self):
97
- """Filter by current user for security."""
98
- return APIKey.objects.filter(user=self.request.user).order_by('-created_at')
99
-
100
- def get_serializer_class(self):
101
- """Use list serializer for list action."""
102
- if self.action == 'list':
103
- return APIKeyListSerializer
104
- return APIKeySerializer
105
-
106
-
107
- # Generic views for specific use cases
108
- class APIKeyCreateView(generics.CreateAPIView):
109
- """Generic view to create API key."""
110
-
111
- serializer_class = APIKeyCreateSerializer
112
- permission_classes = [permissions.IsAuthenticated]
113
-
114
- def perform_create(self, serializer):
115
- """Set current user and generate key when creating."""
116
- import secrets
117
- key_value = f"ak_{secrets.token_urlsafe(32)}"
118
- serializer.save(user=self.request.user, key_value=key_value)
119
-
120
-
121
- class APIKeyValidateView(generics.GenericAPIView):
122
- """Generic view to validate API key."""
123
-
124
- serializer_class = APIKeySerializer # For schema generation
125
- permission_classes = [permissions.AllowAny] # Public endpoint
126
-
127
- def post(self, request):
128
- """Validate API key."""
129
- key_value = request.data.get('api_key')
130
-
131
- if not key_value:
132
- return Response(
133
- {'error': 'API key required'},
134
- status=status.HTTP_400_BAD_REQUEST
135
- )
136
-
137
- try:
138
- api_key = APIKey.objects.get(key_value=key_value, is_active=True)
139
-
140
- # Check if expired
141
- if api_key.is_expired:
142
- return Response(
143
- {'error': 'API key expired'},
144
- status=status.HTTP_401_UNAUTHORIZED
145
- )
146
-
147
- # Update last used
148
- from django.utils import timezone
149
- api_key.last_used = timezone.now()
150
- api_key.save()
151
-
152
- return Response({
153
- 'valid': True,
154
- 'user_id': api_key.user.id,
155
- 'usage_count': api_key.usage_count,
156
- 'expires_at': api_key.expires_at,
157
- 'is_active': api_key.is_active,
158
- })
159
-
160
- except APIKey.DoesNotExist:
161
- return Response(
162
- {'valid': False, 'error': 'Invalid API key'},
163
- status=status.HTTP_401_UNAUTHORIZED
164
- )
@@ -1,75 +0,0 @@
1
- """
2
- Balance ViewSets.
3
- """
4
-
5
- from rest_framework import viewsets, permissions, status
6
- from rest_framework.decorators import action
7
- from rest_framework.response import Response
8
- from django_filters.rest_framework import DjangoFilterBackend
9
- from ..models import UserBalance, Transaction
10
- from ..serializers import (
11
- UserBalanceSerializer, TransactionSerializer, TransactionListSerializer
12
- )
13
-
14
-
15
- class UserBalanceViewSet(viewsets.ReadOnlyModelViewSet):
16
- """User balance ViewSet - read only."""
17
-
18
- queryset = UserBalance.objects.all()
19
- serializer_class = UserBalanceSerializer
20
- permission_classes = [permissions.IsAuthenticated]
21
-
22
- def get_queryset(self):
23
- """Filter by current user."""
24
- return UserBalance.objects.filter(user=self.request.user)
25
-
26
- @action(detail=False, methods=['get'])
27
- def current(self, request):
28
- """Get current user balance."""
29
- balance, _ = UserBalance.objects.get_or_create(
30
- user=request.user,
31
- defaults={'amount_usd': 0.0, 'reserved_usd': 0.0}
32
- )
33
- serializer = self.get_serializer(balance)
34
- return Response(serializer.data)
35
-
36
-
37
- class TransactionViewSet(viewsets.ReadOnlyModelViewSet):
38
- """Transaction ViewSet - read only."""
39
-
40
- queryset = Transaction.objects.all()
41
- serializer_class = TransactionSerializer
42
- permission_classes = [permissions.IsAuthenticated]
43
- filter_backends = [DjangoFilterBackend]
44
- filterset_fields = ['transaction_type', 'payment', 'subscription']
45
-
46
- def get_queryset(self):
47
- """Filter by current user."""
48
- return Transaction.objects.filter(user=self.request.user).order_by('-created_at')
49
-
50
- def get_serializer_class(self):
51
- """Use list serializer for list action."""
52
- if self.action == 'list':
53
- return TransactionListSerializer
54
- return TransactionSerializer
55
-
56
- @action(detail=False, methods=['get'])
57
- def summary(self, request):
58
- """Get transaction summary."""
59
- queryset = self.get_queryset()
60
-
61
- total_earned = sum(
62
- t.amount_usd for t in queryset
63
- if t.amount_usd > 0
64
- )
65
- total_spent = sum(
66
- abs(t.amount_usd) for t in queryset
67
- if t.amount_usd < 0
68
- )
69
-
70
- return Response({
71
- 'total_transactions': queryset.count(),
72
- 'total_earned': total_earned,
73
- 'total_spent': total_spent,
74
- 'net_balance': total_earned - total_spent
75
- })