django-cfg 1.2.22__py3-none-any.whl → 1.2.23__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 (67) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/__init__.py +23 -0
  3. django_cfg/apps/payments/admin/api_keys_admin.py +347 -0
  4. django_cfg/apps/payments/admin/balance_admin.py +434 -0
  5. django_cfg/apps/payments/admin/currencies_admin.py +186 -0
  6. django_cfg/apps/payments/admin/filters.py +259 -0
  7. django_cfg/apps/payments/admin/payments_admin.py +142 -0
  8. django_cfg/apps/payments/admin/subscriptions_admin.py +227 -0
  9. django_cfg/apps/payments/admin/tariffs_admin.py +199 -0
  10. django_cfg/apps/payments/config/__init__.py +87 -0
  11. django_cfg/apps/payments/config/module.py +162 -0
  12. django_cfg/apps/payments/config/providers.py +93 -0
  13. django_cfg/apps/payments/config/settings.py +136 -0
  14. django_cfg/apps/payments/config/utils.py +198 -0
  15. django_cfg/apps/payments/decorators.py +291 -0
  16. django_cfg/apps/payments/middleware/api_access.py +261 -0
  17. django_cfg/apps/payments/middleware/rate_limiting.py +216 -0
  18. django_cfg/apps/payments/middleware/usage_tracking.py +296 -0
  19. django_cfg/apps/payments/migrations/0001_initial.py +32 -11
  20. django_cfg/apps/payments/models/__init__.py +18 -0
  21. django_cfg/apps/payments/models/api_keys.py +2 -2
  22. django_cfg/apps/payments/models/balance.py +2 -2
  23. django_cfg/apps/payments/models/base.py +16 -0
  24. django_cfg/apps/payments/models/events.py +2 -2
  25. django_cfg/apps/payments/models/payments.py +2 -2
  26. django_cfg/apps/payments/models/subscriptions.py +2 -2
  27. django_cfg/apps/payments/services/__init__.py +58 -7
  28. django_cfg/apps/payments/services/billing/__init__.py +8 -0
  29. django_cfg/apps/payments/services/cache/__init__.py +15 -0
  30. django_cfg/apps/payments/services/cache/base.py +30 -0
  31. django_cfg/apps/payments/services/cache/simple_cache.py +135 -0
  32. django_cfg/apps/payments/services/core/__init__.py +17 -0
  33. django_cfg/apps/payments/services/core/balance_service.py +449 -0
  34. django_cfg/apps/payments/services/core/payment_service.py +393 -0
  35. django_cfg/apps/payments/services/core/subscription_service.py +616 -0
  36. django_cfg/apps/payments/services/internal_types.py +266 -0
  37. django_cfg/apps/payments/services/middleware/__init__.py +8 -0
  38. django_cfg/apps/payments/services/providers/__init__.py +19 -0
  39. django_cfg/apps/payments/services/providers/base.py +137 -0
  40. django_cfg/apps/payments/services/providers/cryptapi.py +262 -0
  41. django_cfg/apps/payments/services/providers/nowpayments.py +293 -0
  42. django_cfg/apps/payments/services/providers/registry.py +99 -0
  43. django_cfg/apps/payments/services/validators/__init__.py +8 -0
  44. django_cfg/apps/payments/signals/__init__.py +13 -0
  45. django_cfg/apps/payments/signals/api_key_signals.py +150 -0
  46. django_cfg/apps/payments/signals/payment_signals.py +127 -0
  47. django_cfg/apps/payments/signals/subscription_signals.py +196 -0
  48. django_cfg/apps/payments/urls.py +5 -5
  49. django_cfg/apps/payments/utils/__init__.py +42 -0
  50. django_cfg/apps/payments/utils/config_utils.py +243 -0
  51. django_cfg/apps/payments/utils/middleware_utils.py +228 -0
  52. django_cfg/apps/payments/utils/validation_utils.py +94 -0
  53. django_cfg/apps/support/signals.py +16 -4
  54. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  55. django_cfg/models/revolution.py +1 -1
  56. django_cfg/modules/base.py +1 -1
  57. django_cfg/modules/django_email.py +42 -4
  58. django_cfg/modules/django_unfold/dashboard.py +20 -0
  59. {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/METADATA +2 -1
  60. {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/RECORD +63 -26
  61. django_cfg/apps/payments/services/base.py +0 -68
  62. django_cfg/apps/payments/services/nowpayments.py +0 -78
  63. django_cfg/apps/payments/services/providers.py +0 -77
  64. django_cfg/apps/payments/services/redis_service.py +0 -215
  65. {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/WHEEL +0 -0
  66. {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/entry_points.txt +0 -0
  67. {django_cfg-1.2.22.dist-info → django_cfg-1.2.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,296 @@
1
+ """
2
+ Usage Tracking Middleware.
3
+ Tracks API usage for billing, analytics, and monitoring.
4
+ """
5
+
6
+ import logging
7
+ import json
8
+ from typing import Optional, Dict, Any
9
+ from django.http import HttpRequest, HttpResponse
10
+ from django.utils.deprecation import MiddlewareMixin
11
+ from django.conf import settings
12
+ from django.utils import timezone
13
+ from ..models import APIKey, Subscription, Transaction
14
+ from ..services import RateLimitCache
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class UsageTrackingMiddleware(MiddlewareMixin):
20
+ """
21
+ Middleware for tracking API usage and creating billing records.
22
+
23
+ Features:
24
+ - Request/response logging
25
+ - Usage analytics
26
+ - Billing event creation
27
+ - Performance monitoring
28
+ - Error tracking
29
+ """
30
+
31
+ def __init__(self, get_response=None):
32
+ super().__init__(get_response)
33
+ self.redis_service = RedisService()
34
+
35
+ # Enable/disable usage tracking
36
+ self.enabled = getattr(settings, 'PAYMENTS_USAGE_TRACKING_ENABLED', True)
37
+
38
+ # Paths to track (empty means track all API paths)
39
+ self.tracked_paths = getattr(settings, 'PAYMENTS_TRACKED_PATHS', [])
40
+
41
+ # Paths to exclude from tracking
42
+ self.excluded_paths = getattr(settings, 'PAYMENTS_EXCLUDED_PATHS', [
43
+ '/admin/',
44
+ '/cfg/',
45
+ '/api/v1/api-key/validate/',
46
+ ])
47
+
48
+ # Track request bodies (be careful with sensitive data)
49
+ self.track_request_body = getattr(settings, 'PAYMENTS_TRACK_REQUEST_BODY', False)
50
+
51
+ # Track response bodies (be careful with large responses)
52
+ self.track_response_body = getattr(settings, 'PAYMENTS_TRACK_RESPONSE_BODY', False)
53
+
54
+ def process_request(self, request: HttpRequest) -> None:
55
+ """Process incoming request for usage tracking."""
56
+
57
+ if not self.enabled:
58
+ return
59
+
60
+ # Skip excluded paths
61
+ if self._is_excluded_path(request):
62
+ return
63
+
64
+ # Only track if we have API key
65
+ if not hasattr(request, 'payment_api_key'):
66
+ return
67
+
68
+ # Record request start time
69
+ request._usage_start_time = timezone.now()
70
+
71
+ # Prepare usage data
72
+ request._usage_data = {
73
+ 'api_key_id': request.payment_api_key.id,
74
+ 'user_id': request.payment_api_key.user.id,
75
+ 'method': request.method,
76
+ 'path': request.path,
77
+ 'query_params': dict(request.GET),
78
+ 'user_agent': request.META.get('HTTP_USER_AGENT', ''),
79
+ 'ip_address': self._get_client_ip(request),
80
+ 'start_time': request._usage_start_time,
81
+ }
82
+
83
+ # Add subscription info if available
84
+ if hasattr(request, 'payment_subscription'):
85
+ request._usage_data.update({
86
+ 'subscription_id': request.payment_subscription.id,
87
+ 'endpoint_group_id': request.payment_subscription.endpoint_group.id,
88
+ 'tier': request.payment_subscription.tier,
89
+ })
90
+
91
+ # Track request body if enabled and safe
92
+ if self.track_request_body and self._is_safe_to_track_body(request):
93
+ try:
94
+ request._usage_data['request_body'] = request.body.decode('utf-8')[:1000] # Limit size
95
+ except:
96
+ pass
97
+
98
+ def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
99
+ """Process response for usage tracking."""
100
+
101
+ if not self.enabled or not hasattr(request, '_usage_data'):
102
+ return response
103
+
104
+ try:
105
+ # Calculate response time
106
+ end_time = timezone.now()
107
+ response_time_ms = int((end_time - request._usage_start_time).total_seconds() * 1000)
108
+
109
+ # Update usage data
110
+ usage_data = request._usage_data
111
+ usage_data.update({
112
+ 'end_time': end_time,
113
+ 'response_time_ms': response_time_ms,
114
+ 'status_code': response.status_code,
115
+ 'response_size': len(response.content) if hasattr(response, 'content') else 0,
116
+ })
117
+
118
+ # Track response body if enabled and safe
119
+ if (self.track_response_body and
120
+ self._is_safe_to_track_response(response) and
121
+ response.status_code < 400):
122
+ try:
123
+ content = response.content.decode('utf-8')[:1000] # Limit size
124
+ usage_data['response_body'] = content
125
+ except:
126
+ pass
127
+
128
+ # Track error details for failed requests
129
+ if response.status_code >= 400:
130
+ usage_data['is_error'] = True
131
+ usage_data['error_category'] = self._categorize_error(response.status_code)
132
+ else:
133
+ usage_data['is_error'] = False
134
+
135
+ # Store in Redis for real-time analytics
136
+ self._store_usage_data(usage_data)
137
+
138
+ # Create billing transaction if needed
139
+ if hasattr(request, 'payment_subscription'):
140
+ self._create_billing_transaction(request.payment_subscription, usage_data)
141
+
142
+ # Log for debugging
143
+ logger.info(
144
+ f"API usage tracked - User: {usage_data['user_id']}, "
145
+ f"Path: {usage_data['path']}, "
146
+ f"Status: {usage_data['status_code']}, "
147
+ f"Time: {response_time_ms}ms"
148
+ )
149
+
150
+ except Exception as e:
151
+ logger.error(f"Error in usage tracking: {e}")
152
+
153
+ return response
154
+
155
+ def _is_excluded_path(self, request: HttpRequest) -> bool:
156
+ """Check if path should be excluded from tracking."""
157
+ path = request.path
158
+
159
+ # Check excluded paths
160
+ for excluded in self.excluded_paths:
161
+ if path.startswith(excluded):
162
+ return True
163
+
164
+ # If tracked_paths is specified, only track those
165
+ if self.tracked_paths:
166
+ return not any(path.startswith(tracked) for tracked in self.tracked_paths)
167
+
168
+ return False
169
+
170
+ def _get_client_ip(self, request: HttpRequest) -> Optional[str]:
171
+ """Get client IP address."""
172
+
173
+ # Check for forwarded headers first
174
+ forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
175
+ if forwarded_for:
176
+ return forwarded_for.split(',')[0].strip()
177
+
178
+ # Check for real IP header
179
+ real_ip = request.META.get('HTTP_X_REAL_IP')
180
+ if real_ip:
181
+ return real_ip
182
+
183
+ # Fallback to remote address
184
+ return request.META.get('REMOTE_ADDR')
185
+
186
+ def _is_safe_to_track_body(self, request: HttpRequest) -> bool:
187
+ """Check if it's safe to track request body."""
188
+
189
+ # Don't track large bodies
190
+ content_length = request.META.get('CONTENT_LENGTH')
191
+ if content_length and int(content_length) > 10000: # 10KB limit
192
+ return False
193
+
194
+ # Don't track file uploads
195
+ content_type = request.META.get('CONTENT_TYPE', '')
196
+ if 'multipart/form-data' in content_type:
197
+ return False
198
+
199
+ # Don't track sensitive endpoints
200
+ sensitive_paths = ['/api/v1/api-key/', '/api/v1/payment/']
201
+ path = request.path
202
+ if any(path.startswith(sensitive) for sensitive in sensitive_paths):
203
+ return False
204
+
205
+ return True
206
+
207
+ def _is_safe_to_track_response(self, response: HttpResponse) -> bool:
208
+ """Check if it's safe to track response body."""
209
+
210
+ # Don't track large responses
211
+ if hasattr(response, 'content') and len(response.content) > 10000: # 10KB limit
212
+ return False
213
+
214
+ # Only track JSON responses
215
+ content_type = response.get('Content-Type', '')
216
+ if 'application/json' not in content_type:
217
+ return False
218
+
219
+ return True
220
+
221
+ def _categorize_error(self, status_code: int) -> str:
222
+ """Categorize error by status code."""
223
+
224
+ if 400 <= status_code < 500:
225
+ return 'client_error'
226
+ elif 500 <= status_code < 600:
227
+ return 'server_error'
228
+ else:
229
+ return 'unknown_error'
230
+
231
+ def _store_usage_data(self, usage_data: Dict[str, Any]):
232
+ """Store usage data in Redis for analytics."""
233
+
234
+ try:
235
+ # Store daily usage stats
236
+ date_key = usage_data['start_time'].strftime('%Y-%m-%d')
237
+ user_id = usage_data['user_id']
238
+
239
+ # Increment counters
240
+ self.redis_service.increment_daily_usage(user_id, date_key)
241
+
242
+ if usage_data.get('subscription_id'):
243
+ self.redis_service.increment_subscription_usage(
244
+ usage_data['subscription_id'],
245
+ date_key
246
+ )
247
+
248
+ # Store performance metrics
249
+ if usage_data['response_time_ms'] > 0:
250
+ self.redis_service.record_response_time(
251
+ usage_data['path'],
252
+ usage_data['response_time_ms']
253
+ )
254
+
255
+ # Store error metrics
256
+ if usage_data.get('is_error'):
257
+ self.redis_service.increment_error_count(
258
+ usage_data['path'],
259
+ usage_data['status_code']
260
+ )
261
+
262
+ except Exception as e:
263
+ logger.error(f"Error storing usage data in Redis: {e}")
264
+
265
+ def _create_billing_transaction(self, subscription: Subscription, usage_data: Dict[str, Any]):
266
+ """Create billing transaction for usage-based pricing."""
267
+
268
+ try:
269
+ # Only create transaction for successful requests
270
+ if usage_data.get('is_error'):
271
+ return
272
+
273
+ # Check if this endpoint has usage-based pricing
274
+ # For now, we'll create a small transaction for each API call
275
+ # This could be batched or calculated differently based on business logic
276
+
277
+ cost_per_request = 0.001 # $0.001 per request (example)
278
+
279
+ # Create transaction record
280
+ Transaction.objects.create(
281
+ user=subscription.user,
282
+ subscription=subscription,
283
+ transaction_type='debit',
284
+ amount_usd=-cost_per_request, # Negative for debit
285
+ description=f"API usage: {usage_data['method']} {usage_data['path']}",
286
+ metadata={
287
+ 'api_call_id': f"{usage_data['api_key_id']}_{usage_data['start_time'].timestamp()}",
288
+ 'endpoint': usage_data['path'],
289
+ 'method': usage_data['method'],
290
+ 'response_time_ms': usage_data['response_time_ms'],
291
+ 'status_code': usage_data['status_code'],
292
+ }
293
+ )
294
+
295
+ except Exception as e:
296
+ logger.error(f"Error creating billing transaction: {e}")
@@ -1,7 +1,8 @@
1
- # Generated by Django 5.2.6 on 2025-09-23 13:36
1
+ # Generated by Django 5.2.6 on 2025-09-23 14:27
2
2
 
3
3
  import django.core.validators
4
4
  import django.db.models.deletion
5
+ import uuid
5
6
  from django.conf import settings
6
7
  from django.db import migrations, models
7
8
 
@@ -172,8 +173,12 @@ class Migration(migrations.Migration):
172
173
  fields=[
173
174
  (
174
175
  "id",
175
- models.BigAutoField(
176
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
176
+ models.UUIDField(
177
+ default=uuid.uuid4,
178
+ editable=False,
179
+ help_text="Unique identifier",
180
+ primary_key=True,
181
+ serialize=False,
177
182
  ),
178
183
  ),
179
184
  ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
@@ -252,8 +257,12 @@ class Migration(migrations.Migration):
252
257
  fields=[
253
258
  (
254
259
  "id",
255
- models.BigAutoField(
256
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
260
+ models.UUIDField(
261
+ default=uuid.uuid4,
262
+ editable=False,
263
+ help_text="Unique identifier",
264
+ primary_key=True,
265
+ serialize=False,
257
266
  ),
258
267
  ),
259
268
  ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
@@ -442,8 +451,12 @@ class Migration(migrations.Migration):
442
451
  fields=[
443
452
  (
444
453
  "id",
445
- models.BigAutoField(
446
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
454
+ models.UUIDField(
455
+ default=uuid.uuid4,
456
+ editable=False,
457
+ help_text="Unique identifier",
458
+ primary_key=True,
459
+ serialize=False,
447
460
  ),
448
461
  ),
449
462
  ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
@@ -606,8 +619,12 @@ class Migration(migrations.Migration):
606
619
  fields=[
607
620
  (
608
621
  "id",
609
- models.BigAutoField(
610
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
622
+ models.UUIDField(
623
+ default=uuid.uuid4,
624
+ editable=False,
625
+ help_text="Unique identifier",
626
+ primary_key=True,
627
+ serialize=False,
611
628
  ),
612
629
  ),
613
630
  ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
@@ -767,8 +784,12 @@ class Migration(migrations.Migration):
767
784
  fields=[
768
785
  (
769
786
  "id",
770
- models.BigAutoField(
771
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
787
+ models.UUIDField(
788
+ default=uuid.uuid4,
789
+ editable=False,
790
+ help_text="Unique identifier",
791
+ primary_key=True,
792
+ serialize=False,
772
793
  ),
773
794
  ),
774
795
  ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
@@ -28,6 +28,15 @@ from .api_keys import APIKey
28
28
  # Event sourcing
29
29
  from .events import PaymentEvent
30
30
 
31
+ # TextChoices classes for external use (accessing inner classes)
32
+ CurrencyType = Currency.CurrencyType
33
+ PaymentStatus = UniversalPayment.PaymentStatus
34
+ PaymentProvider = UniversalPayment.PaymentProvider
35
+ TransactionType = Transaction.TransactionType
36
+ SubscriptionStatus = Subscription.SubscriptionStatus
37
+ SubscriptionTier = Subscription.SubscriptionTier
38
+ EventType = PaymentEvent.EventType
39
+
31
40
  __all__ = [
32
41
  # Base
33
42
  'TimestampedModel',
@@ -46,4 +55,13 @@ __all__ = [
46
55
  'TariffEndpointGroup',
47
56
  'APIKey',
48
57
  'PaymentEvent',
58
+
59
+ # TextChoices
60
+ 'CurrencyType',
61
+ 'PaymentStatus',
62
+ 'PaymentProvider',
63
+ 'TransactionType',
64
+ 'SubscriptionStatus',
65
+ 'SubscriptionTier',
66
+ 'EventType',
49
67
  ]
@@ -5,12 +5,12 @@ API key models for the universal payments system.
5
5
  from django.db import models
6
6
  from django.contrib.auth import get_user_model
7
7
  from django.utils import timezone
8
- from .base import TimestampedModel
8
+ from .base import UUIDTimestampedModel
9
9
 
10
10
  User = get_user_model()
11
11
 
12
12
 
13
- class APIKey(TimestampedModel):
13
+ class APIKey(UUIDTimestampedModel):
14
14
  """API keys for authentication and usage tracking."""
15
15
 
16
16
  user = models.ForeignKey(
@@ -7,7 +7,7 @@ from django.contrib.auth import get_user_model
7
7
  from django.core.validators import MinValueValidator
8
8
  from django.core.exceptions import ValidationError
9
9
  from django.utils import timezone
10
- from .base import TimestampedModel
10
+ from .base import UUIDTimestampedModel, TimestampedModel
11
11
 
12
12
  User = get_user_model()
13
13
 
@@ -89,7 +89,7 @@ class UserBalance(TimestampedModel):
89
89
  }
90
90
 
91
91
 
92
- class Transaction(TimestampedModel):
92
+ class Transaction(UUIDTimestampedModel):
93
93
  """Transaction history model."""
94
94
 
95
95
  class TransactionType(models.TextChoices):
@@ -2,6 +2,7 @@
2
2
  Base model classes for the universal payments system.
3
3
  """
4
4
 
5
+ import uuid
5
6
  from django.db import models
6
7
 
7
8
 
@@ -10,5 +11,20 @@ class TimestampedModel(models.Model):
10
11
  created_at = models.DateTimeField(auto_now_add=True, db_index=True)
11
12
  updated_at = models.DateTimeField(auto_now=True)
12
13
 
14
+ class Meta:
15
+ abstract = True
16
+
17
+
18
+ class UUIDTimestampedModel(models.Model):
19
+ """Base model with UUID primary key and automatic timestamps."""
20
+ id = models.UUIDField(
21
+ primary_key=True,
22
+ default=uuid.uuid4,
23
+ editable=False,
24
+ help_text="Unique identifier"
25
+ )
26
+ created_at = models.DateTimeField(auto_now_add=True, db_index=True)
27
+ updated_at = models.DateTimeField(auto_now=True)
28
+
13
29
  class Meta:
14
30
  abstract = True
@@ -3,10 +3,10 @@ Event sourcing models for the universal payments system.
3
3
  """
4
4
 
5
5
  from django.db import models
6
- from .base import TimestampedModel
6
+ from .base import UUIDTimestampedModel
7
7
 
8
8
 
9
- class PaymentEvent(TimestampedModel):
9
+ class PaymentEvent(UUIDTimestampedModel):
10
10
  """Event sourcing for payment operations - immutable audit trail."""
11
11
 
12
12
  class EventType(models.TextChoices):
@@ -7,13 +7,13 @@ from django.contrib.auth import get_user_model
7
7
  from django.core.validators import MinValueValidator
8
8
  from django.core.exceptions import ValidationError
9
9
  from django.utils import timezone
10
- from .base import TimestampedModel
10
+ from .base import UUIDTimestampedModel
11
11
 
12
12
  User = get_user_model()
13
13
 
14
14
 
15
15
 
16
- class UniversalPayment(TimestampedModel):
16
+ class UniversalPayment(UUIDTimestampedModel):
17
17
  """Universal payment model for all providers."""
18
18
 
19
19
  class PaymentStatus(models.TextChoices):
@@ -7,7 +7,7 @@ from django.contrib.auth import get_user_model
7
7
  from django.core.validators import MinValueValidator
8
8
  from django.utils import timezone
9
9
  from datetime import timedelta
10
- from .base import TimestampedModel
10
+ from .base import UUIDTimestampedModel, TimestampedModel
11
11
 
12
12
  User = get_user_model()
13
13
 
@@ -106,7 +106,7 @@ class EndpointGroup(TimestampedModel):
106
106
  return tier_limits.get(tier, 0)
107
107
 
108
108
 
109
- class Subscription(TimestampedModel):
109
+ class Subscription(UUIDTimestampedModel):
110
110
  """User subscriptions to endpoint groups."""
111
111
 
112
112
  class SubscriptionStatus(models.TextChoices):
@@ -1,14 +1,65 @@
1
1
  """
2
- Universal payment services.
2
+ Universal Payment Services.
3
+
4
+ Modular architecture with minimal Pydantic typing for inter-service communication.
5
+ Uses Django ORM for data persistence and DRF for API responses.
3
6
  """
4
7
 
5
- from .base import PaymentProvider, PaymentService
6
- from .nowpayments import NowPaymentsProvider
7
- from .redis_service import RedisService
8
+ # Core services
9
+ from .core.payment_service import PaymentService
10
+ from .core.balance_service import BalanceService
11
+ from .core.subscription_service import SubscriptionService
12
+
13
+ # Provider services
14
+ from .providers.registry import ProviderRegistry
15
+ from .providers.nowpayments import NowPaymentsProvider
16
+ from .providers.cryptapi import CryptAPIProvider
17
+
18
+ # Cache services
19
+ from .cache import SimpleCache, ApiKeyCache, RateLimitCache
20
+
21
+ # Internal types for inter-service communication
22
+ from .internal_types import (
23
+ ProviderResponse, WebhookData, ServiceOperationResult,
24
+ BalanceUpdateRequest, AccessCheckRequest, AccessCheckResult,
25
+ # Service response models
26
+ PaymentCreationResult, WebhookProcessingResult, PaymentStatusResult,
27
+ UserBalanceResult, TransferResult, TransactionInfo,
28
+ EndpointGroupInfo, SubscriptionInfo, SubscriptionAnalytics
29
+ )
8
30
 
9
31
  __all__ = [
10
- 'PaymentProvider',
11
- 'PaymentService',
32
+ # Core services
33
+ 'PaymentService',
34
+ 'BalanceService',
35
+ 'SubscriptionService',
36
+
37
+ # Provider services
38
+ 'ProviderRegistry',
12
39
  'NowPaymentsProvider',
13
- 'RedisService',
40
+ 'CryptAPIProvider',
41
+
42
+ # Cache services
43
+ 'SimpleCache',
44
+ 'ApiKeyCache',
45
+ 'RateLimitCache',
46
+
47
+ # Internal types
48
+ 'ProviderResponse',
49
+ 'WebhookData',
50
+ 'ServiceOperationResult',
51
+ 'BalanceUpdateRequest',
52
+ 'AccessCheckRequest',
53
+ 'AccessCheckResult',
54
+
55
+ # Service response models
56
+ 'PaymentCreationResult',
57
+ 'WebhookProcessingResult',
58
+ 'PaymentStatusResult',
59
+ 'UserBalanceResult',
60
+ 'TransferResult',
61
+ 'TransactionInfo',
62
+ 'EndpointGroupInfo',
63
+ 'SubscriptionInfo',
64
+ 'SubscriptionAnalytics',
14
65
  ]
@@ -0,0 +1,8 @@
1
+ """
2
+ Billing services for payments module.
3
+
4
+ TODO: Implement billing services when needed.
5
+ """
6
+
7
+ # Placeholder for future billing services
8
+ __all__ = []
@@ -0,0 +1,15 @@
1
+ """
2
+ Simple caching for API key access control and rate limiting.
3
+
4
+ ONLY for API key caching - NOT for payment data!
5
+ """
6
+
7
+ from .base import CacheInterface
8
+ from .simple_cache import SimpleCache, ApiKeyCache, RateLimitCache
9
+
10
+ __all__ = [
11
+ 'CacheInterface',
12
+ 'SimpleCache',
13
+ 'ApiKeyCache',
14
+ 'RateLimitCache',
15
+ ]
@@ -0,0 +1,30 @@
1
+ """
2
+ Base cache interface for payments module.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Optional, Any
7
+
8
+
9
+ class CacheInterface(ABC):
10
+ """Abstract cache interface."""
11
+
12
+ @abstractmethod
13
+ def get(self, key: str) -> Optional[Any]:
14
+ """Get value from cache."""
15
+ pass
16
+
17
+ @abstractmethod
18
+ def set(self, key: str, value: Any, timeout: Optional[int] = None) -> bool:
19
+ """Set value in cache."""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def delete(self, key: str) -> bool:
24
+ """Delete value from cache."""
25
+ pass
26
+
27
+ @abstractmethod
28
+ def exists(self, key: str) -> bool:
29
+ """Check if key exists in cache."""
30
+ pass