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,342 +0,0 @@
1
- """
2
- Basic billing utilities for production use.
3
-
4
- Provides essential billing calculations and transaction management
5
- without over-engineering.
6
- """
7
-
8
- import logging
9
- from typing import Dict, Any, Optional, Tuple
10
- from decimal import Decimal, ROUND_HALF_UP
11
- from datetime import datetime, timedelta
12
- from django.utils import timezone
13
- from django.db import transaction
14
- from django.contrib.auth import get_user_model
15
-
16
- from ..models import UserBalance, Transaction, Subscription
17
-
18
- User = get_user_model()
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- def calculate_usage_cost(
23
- subscription: Subscription,
24
- usage_count: int,
25
- billing_period: str = 'monthly'
26
- ) -> Decimal:
27
- """
28
- Calculate cost for API usage.
29
-
30
- Args:
31
- subscription: User subscription
32
- usage_count: Number of API calls
33
- billing_period: Billing period (monthly/yearly)
34
-
35
- Returns:
36
- Cost in USD
37
- """
38
- try:
39
- endpoint_group = subscription.endpoint_group
40
-
41
- # Get base price
42
- if billing_period == 'monthly':
43
- base_price = endpoint_group.monthly_price_usd
44
- limit = endpoint_group.monthly_request_limit
45
- else:
46
- base_price = endpoint_group.yearly_price_usd
47
- limit = endpoint_group.yearly_request_limit or (endpoint_group.monthly_request_limit * 12)
48
-
49
- # If usage is within limit, cost is covered by subscription
50
- if usage_count <= limit:
51
- return Decimal('0.00')
52
-
53
- # Calculate overage cost
54
- overage = usage_count - limit
55
- overage_rate = getattr(endpoint_group, 'overage_rate_per_request', Decimal('0.01'))
56
-
57
- overage_cost = Decimal(overage) * overage_rate
58
-
59
- # Round to 2 decimal places
60
- return overage_cost.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
61
-
62
- except Exception as e:
63
- logger.error(f"Error calculating usage cost: {e}")
64
- return Decimal('0.00')
65
-
66
-
67
- def create_billing_transaction(
68
- user: User,
69
- amount: Decimal,
70
- transaction_type: str,
71
- source: str = 'billing',
72
- description: Optional[str] = None,
73
- reference_id: Optional[str] = None,
74
- metadata: Optional[Dict[str, Any]] = None
75
- ) -> Tuple[bool, Optional[Transaction]]:
76
- """
77
- Create a billing transaction with balance update.
78
-
79
- Args:
80
- user: User object
81
- amount: Transaction amount (positive for credit, negative for debit)
82
- transaction_type: Type of transaction
83
- source: Source of transaction
84
- description: Human-readable description
85
- reference_id: External reference ID
86
- metadata: Additional metadata
87
-
88
- Returns:
89
- Tuple of (success, transaction)
90
- """
91
- try:
92
- with transaction.atomic():
93
- # Get or create user balance
94
- balance, created = UserBalance.objects.get_or_create(
95
- user=user,
96
- currency_id=1, # Assuming USD currency has ID 1
97
- defaults={
98
- 'available_amount': Decimal('0.00'),
99
- 'held_amount': Decimal('0.00')
100
- }
101
- )
102
-
103
- # Check if debit is possible
104
- if amount < 0 and not balance.can_debit(abs(amount)):
105
- logger.warning(f"Insufficient balance for user {user.id}: {balance.available_amount} < {abs(amount)}")
106
- return False, None
107
-
108
- # Calculate new balance
109
- old_balance = balance.available_amount
110
- new_balance = old_balance + amount
111
-
112
- # Update balance
113
- balance.available_amount = new_balance
114
-
115
- # Update totals
116
- if amount > 0:
117
- balance.total_earned += amount
118
- else:
119
- balance.total_spent += abs(amount)
120
-
121
- balance.save()
122
-
123
- # Create transaction record
124
- txn = Transaction.objects.create(
125
- user=user,
126
- balance=balance,
127
- transaction_type=transaction_type,
128
- amount=amount,
129
- balance_before=old_balance,
130
- balance_after=new_balance,
131
- source=source,
132
- description=description or f"{transaction_type} transaction",
133
- reference_id=reference_id,
134
- metadata=metadata or {}
135
- )
136
-
137
- logger.info(f"Created billing transaction: {txn.id} for user {user.id}, amount: {amount}")
138
- return True, txn
139
-
140
- except Exception as e:
141
- logger.error(f"Error creating billing transaction for user {user.id}: {e}")
142
- return False, None
143
-
144
-
145
- def calculate_subscription_refund(
146
- subscription: Subscription,
147
- refund_strategy: str = 'prorated',
148
- cancellation_date: Optional[datetime] = None
149
- ) -> Dict[str, Any]:
150
- """
151
- Calculate refund amount for cancelled subscription.
152
-
153
- Args:
154
- subscription: Subscription to refund
155
- refund_strategy: 'prorated', 'full', or 'none'
156
- cancellation_date: Date of cancellation (defaults to now)
157
-
158
- Returns:
159
- Dict with refund calculation details
160
- """
161
- try:
162
- if not cancellation_date:
163
- cancellation_date = timezone.now()
164
-
165
- # Get subscription details
166
- start_date = subscription.starts_at
167
- end_date = subscription.expires_at
168
-
169
- if subscription.billing_period == 'monthly':
170
- original_amount = subscription.endpoint_group.monthly_price_usd
171
- else:
172
- original_amount = subscription.endpoint_group.yearly_price_usd
173
-
174
- # Calculate refund based on strategy
175
- if refund_strategy == 'none':
176
- refund_amount = Decimal('0.00')
177
- refund_reason = "No refund policy"
178
-
179
- elif refund_strategy == 'full':
180
- refund_amount = original_amount
181
- refund_reason = "Full refund"
182
-
183
- elif refund_strategy == 'prorated':
184
- # Calculate prorated refund
185
- total_days = (end_date - start_date).days
186
- used_days = (cancellation_date - start_date).days
187
- remaining_days = max(0, total_days - used_days)
188
-
189
- if total_days > 0:
190
- refund_percentage = Decimal(remaining_days) / Decimal(total_days)
191
- refund_amount = (original_amount * refund_percentage).quantize(
192
- Decimal('0.01'), rounding=ROUND_HALF_UP
193
- )
194
- else:
195
- refund_amount = Decimal('0.00')
196
-
197
- refund_reason = f"Prorated refund: {remaining_days}/{total_days} days remaining"
198
-
199
- else:
200
- refund_amount = Decimal('0.00')
201
- refund_reason = "Unknown refund strategy"
202
-
203
- return {
204
- 'refund_amount': refund_amount,
205
- 'original_amount': original_amount,
206
- 'refund_strategy': refund_strategy,
207
- 'refund_reason': refund_reason,
208
- 'calculation_date': cancellation_date.isoformat(),
209
- 'subscription_id': str(subscription.id),
210
- 'billing_period': subscription.billing_period
211
- }
212
-
213
- except Exception as e:
214
- logger.error(f"Error calculating refund for subscription {subscription.id}: {e}")
215
- return {
216
- 'refund_amount': Decimal('0.00'),
217
- 'original_amount': Decimal('0.00'),
218
- 'refund_strategy': refund_strategy,
219
- 'refund_reason': f"Calculation error: {str(e)}",
220
- 'error': True
221
- }
222
-
223
-
224
- def process_subscription_billing(subscription: Subscription) -> Dict[str, Any]:
225
- """
226
- Process billing for subscription renewal.
227
-
228
- Args:
229
- subscription: Subscription to bill
230
-
231
- Returns:
232
- Dict with billing results
233
- """
234
- try:
235
- # Calculate billing amount
236
- if subscription.billing_period == 'monthly':
237
- amount = subscription.endpoint_group.monthly_price_usd
238
- billing_period_days = 30
239
- else:
240
- amount = subscription.endpoint_group.yearly_price_usd
241
- billing_period_days = 365
242
-
243
- # Create billing transaction
244
- success, txn = create_billing_transaction(
245
- user=subscription.user,
246
- amount=-amount, # Negative for debit
247
- transaction_type='subscription_billing',
248
- source='subscription_renewal',
249
- description=f"Subscription renewal: {subscription.endpoint_group.display_name}",
250
- reference_id=str(subscription.id),
251
- metadata={
252
- 'subscription_id': str(subscription.id),
253
- 'billing_period': subscription.billing_period,
254
- 'endpoint_group': subscription.endpoint_group.name
255
- }
256
- )
257
-
258
- if success:
259
- # Update subscription
260
- subscription.next_billing_at = timezone.now() + timedelta(days=billing_period_days)
261
- subscription.current_usage = 0 # Reset usage
262
- subscription.save()
263
-
264
- logger.info(f"Successfully billed subscription {subscription.id} for ${amount}")
265
-
266
- return {
267
- 'success': True,
268
- 'amount_billed': amount,
269
- 'transaction_id': str(txn.id),
270
- 'next_billing_at': subscription.next_billing_at.isoformat()
271
- }
272
- else:
273
- logger.warning(f"Failed to bill subscription {subscription.id}: insufficient balance")
274
-
275
- return {
276
- 'success': False,
277
- 'error': 'Insufficient balance',
278
- 'amount_required': amount,
279
- 'user_balance': UserBalance.objects.get(user=subscription.user).available_amount
280
- }
281
-
282
- except Exception as e:
283
- logger.error(f"Error processing subscription billing {subscription.id}: {e}")
284
- return {
285
- 'success': False,
286
- 'error': str(e)
287
- }
288
-
289
-
290
- def get_billing_summary(user: User, days: int = 30) -> Dict[str, Any]:
291
- """
292
- Get billing summary for user over specified period.
293
-
294
- Args:
295
- user: User object
296
- days: Number of days to include
297
-
298
- Returns:
299
- Dict with billing summary
300
- """
301
- try:
302
- cutoff_date = timezone.now() - timedelta(days=days)
303
-
304
- # Get transactions
305
- transactions = Transaction.objects.filter(
306
- user=user,
307
- created_at__gte=cutoff_date
308
- )
309
-
310
- # Calculate totals
311
- from django.db import models
312
-
313
- total_credits = transactions.filter(amount__gt=0).aggregate(
314
- total=models.Sum('amount')
315
- )['total'] or Decimal('0.00')
316
-
317
- total_debits = transactions.filter(amount__lt=0).aggregate(
318
- total=models.Sum('amount')
319
- )['total'] or Decimal('0.00')
320
-
321
- # Get current balance
322
- try:
323
- balance = UserBalance.objects.get(user=user)
324
- current_balance = balance.available_amount
325
- except UserBalance.DoesNotExist:
326
- current_balance = Decimal('0.00')
327
-
328
- return {
329
- 'period_days': days,
330
- 'total_credits': total_credits,
331
- 'total_debits': abs(total_debits),
332
- 'net_change': total_credits + total_debits, # total_debits is negative
333
- 'current_balance': current_balance,
334
- 'transaction_count': transactions.count()
335
- }
336
-
337
- except Exception as e:
338
- logger.error(f"Error getting billing summary for user {user.id}: {e}")
339
- return {
340
- 'error': str(e),
341
- 'period_days': days
342
- }
@@ -1,245 +0,0 @@
1
- """
2
- Configuration utilities for payments module.
3
-
4
- Universal utilities for working with django-cfg settings and configuration.
5
- """
6
-
7
- import logging
8
- from typing import Optional, Dict, Any, Type
9
- from django.conf import settings
10
-
11
- from django_cfg.modules.base import BaseCfgModule
12
- from ..config.settings import PaymentsSettings
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class PaymentsConfigMixin:
18
- """Mixin for accessing payments configuration through django-cfg."""
19
-
20
- _payments_config_cache: Optional[PaymentsSettings] = None
21
- _config_module: Optional[BaseCfgModule] = None
22
-
23
- @classmethod
24
- def get_payments_config(cls) -> PaymentsSettings:
25
- """Get payments configuration from django-cfg."""
26
- if cls._payments_config_cache is None:
27
- cls._payments_config_cache = cls._load_payments_config()
28
- return cls._payments_config_cache
29
-
30
- @classmethod
31
- def _load_payments_config(cls) -> PaymentsSettings:
32
- """Load payments configuration using BaseCfgModule."""
33
- try:
34
- if cls._config_module is None:
35
- from ..config.module import PaymentsCfgModule
36
- cls._config_module = PaymentsCfgModule()
37
-
38
- return cls._config_module.get_config()
39
- except Exception as e:
40
- logger.warning(f"Failed to load payments config: {e}")
41
- return PaymentsSettings()
42
-
43
- @classmethod
44
- def reset_config_cache(cls):
45
- """Reset configuration cache."""
46
- cls._payments_config_cache = None
47
- if cls._config_module:
48
- cls._config_module.reset_cache()
49
-
50
-
51
- class RedisConfigHelper(PaymentsConfigMixin):
52
- """Helper for Redis configuration."""
53
-
54
- @classmethod
55
- def get_redis_config(cls) -> Dict[str, Any]:
56
- """Get Redis configuration for payments."""
57
- config = cls.get_payments_config()
58
-
59
- # Default Redis settings
60
- redis_config = {
61
- 'host': 'localhost',
62
- 'port': 6379,
63
- 'db': 0,
64
- 'decode_responses': True,
65
- 'socket_timeout': 5,
66
- 'socket_connect_timeout': 5,
67
- 'retry_on_timeout': True,
68
- 'health_check_interval': 30,
69
- }
70
-
71
- # Try to get Redis settings from Django CACHES
72
- django_cache = getattr(settings, 'CACHES', {}).get('default', {})
73
- if 'redis' in django_cache.get('BACKEND', '').lower():
74
- location = django_cache.get('LOCATION', '')
75
- if location.startswith('redis://'):
76
- # Parse redis://host:port/db format
77
- try:
78
- # Simple parsing for redis://host:port/db
79
- parts = location.replace('redis://', '').split('/')
80
- host_port = parts[0].split(':')
81
- redis_config['host'] = host_port[0]
82
- if len(host_port) > 1:
83
- redis_config['port'] = int(host_port[1])
84
- if len(parts) > 1:
85
- redis_config['db'] = int(parts[1])
86
- except (ValueError, IndexError) as e:
87
- logger.warning(f"Failed to parse Redis URL {location}: {e}")
88
-
89
- # Override with payments-specific Redis config if available
90
- if hasattr(config, 'redis') and config.redis:
91
- redis_config.update(config.redis.dict())
92
-
93
- return redis_config
94
-
95
- @classmethod
96
- def is_redis_available(cls) -> bool:
97
- """Check if Redis is available and configured."""
98
- try:
99
- import redis
100
- config = cls.get_redis_config()
101
- client = redis.Redis(**config)
102
- client.ping()
103
- return True
104
- except Exception as e:
105
- logger.debug(f"Redis not available: {e}")
106
- return False
107
-
108
-
109
- class CacheConfigHelper(PaymentsConfigMixin):
110
- """Helper for cache configuration."""
111
-
112
- @classmethod
113
- def get_cache_backend_type(cls) -> str:
114
- """Get Django cache backend type."""
115
- django_cache = getattr(settings, 'CACHES', {}).get('default', {})
116
- backend = django_cache.get('BACKEND', '').lower()
117
-
118
- if 'redis' in backend:
119
- return 'redis'
120
- elif 'memcached' in backend:
121
- return 'memcached'
122
- elif 'database' in backend:
123
- return 'database'
124
- elif 'dummy' in backend:
125
- return 'dummy'
126
- else:
127
- return 'unknown'
128
-
129
- @classmethod
130
- def is_cache_enabled(cls) -> bool:
131
- """Check if cache is properly configured (not dummy)."""
132
- return cls.get_cache_backend_type() != 'dummy'
133
-
134
- @classmethod
135
- def get_cache_timeout(cls, operation: str) -> int:
136
- """Get cache timeout for specific operation."""
137
- config = cls.get_payments_config()
138
-
139
- timeouts = {
140
- 'api_key': 300, # 5 minutes
141
- 'rate_limit': 3600, # 1 hour
142
- 'session': 1800, # 30 minutes
143
- 'default': 600 # 10 minutes
144
- }
145
-
146
- # Override with config if available
147
- if hasattr(config, 'cache_timeouts') and config.cache_timeouts:
148
- timeouts.update(config.cache_timeouts)
149
-
150
- return timeouts.get(operation, timeouts['default'])
151
-
152
-
153
- class ProviderConfigHelper(PaymentsConfigMixin):
154
- """Helper for payment provider configuration."""
155
-
156
- @classmethod
157
- def get_enabled_providers(cls) -> list:
158
- """Get list of enabled payment providers."""
159
- config = cls.get_payments_config()
160
- if not config.enabled:
161
- return []
162
-
163
- enabled = []
164
- if hasattr(config, 'providers') and config.providers:
165
- for provider_name, provider_config in config.providers.items():
166
- if provider_config and cls._is_provider_properly_configured(provider_name, provider_config):
167
- enabled.append(provider_name)
168
-
169
- return enabled
170
-
171
- @classmethod
172
- def get_provider_config(cls, provider_name: str) -> Optional[Any]:
173
- """Get configuration for specific provider."""
174
- config = cls.get_payments_config()
175
- if not config.enabled or not hasattr(config, 'providers'):
176
- return None
177
-
178
- return config.providers.get(provider_name)
179
-
180
- @classmethod
181
- def is_provider_enabled(cls, provider_name: str) -> bool:
182
- """Check if specific provider is enabled and configured."""
183
- return provider_name in cls.get_enabled_providers()
184
-
185
- @classmethod
186
- def _is_provider_properly_configured(cls, provider_name: str, provider_config: Any) -> bool:
187
- """Check if provider configuration is complete."""
188
- if not provider_config:
189
- return False
190
-
191
- # Basic validation - each provider should have api_key
192
- if not hasattr(provider_config, 'api_key') or not provider_config.api_key:
193
- return False
194
-
195
- # Provider-specific validations
196
- if provider_name == 'nowpayments':
197
- return True # api_key is sufficient
198
- elif provider_name == 'stripe':
199
- return True # api_key is sufficient
200
- elif provider_name == 'cryptapi':
201
- return hasattr(provider_config, 'own_address') and provider_config.own_address
202
- elif provider_name == 'cryptomus':
203
- return hasattr(provider_config, 'merchant_uuid') and provider_config.merchant_uuid
204
-
205
- return True
206
-
207
-
208
- class PaymentsConfigUtil:
209
- """
210
- Universal utility for payments configuration.
211
-
212
- Combines all config helpers into one convenient interface.
213
- """
214
-
215
- redis = RedisConfigHelper
216
- cache = CacheConfigHelper
217
- providers = ProviderConfigHelper
218
-
219
- @staticmethod
220
- def get_config() -> PaymentsSettings:
221
- """Get payments configuration."""
222
- return PaymentsConfigMixin.get_payments_config()
223
-
224
- @staticmethod
225
- def is_payments_enabled() -> bool:
226
- """Check if payments module is enabled."""
227
- config = PaymentsConfigMixin.get_payments_config()
228
- return config.enabled
229
-
230
- @staticmethod
231
- def is_debug_mode() -> bool:
232
- """Check if payments module is in debug mode."""
233
- config = PaymentsConfigMixin.get_payments_config()
234
- return getattr(config, 'debug_mode', False)
235
-
236
- @staticmethod
237
- def reset_all_caches():
238
- """Reset all configuration caches."""
239
- PaymentsConfigMixin.reset_config_cache()
240
-
241
-
242
- # Convenience exports
243
- get_payments_config = PaymentsConfigUtil.get_config
244
- is_payments_enabled = PaymentsConfigUtil.is_payments_enabled
245
- is_debug_mode = PaymentsConfigUtil.is_debug_mode