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
@@ -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
- from django_cfg.modules.django_logger import get_logger
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 = get_logger("billing_utils")
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,239 +0,0 @@
1
- """
2
- Configuration utilities for payments module.
3
-
4
- Universal utilities for working with django-cfg settings and configuration.
5
- """
6
-
7
- from typing import Optional, Dict, Any, Type
8
- from django.conf import settings
9
-
10
- from django_cfg.modules.base import BaseCfgModule
11
- from ..config.settings import PaymentsSettings
12
- from django_cfg.modules.django_logger import get_logger
13
-
14
- logger = get_logger("config_utils")
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
-
231
- @staticmethod
232
- def reset_all_caches():
233
- """Reset all configuration caches."""
234
- PaymentsConfigMixin.reset_config_cache()
235
-
236
-
237
- # Convenience exports
238
- get_payments_config = PaymentsConfigUtil.get_config
239
- is_payments_enabled = PaymentsConfigUtil.is_payments_enabled