django-cfg 1.2.21__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 (92) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/newsletter/signals.py +9 -8
  3. django_cfg/apps/payments/__init__.py +8 -0
  4. django_cfg/apps/payments/admin/__init__.py +23 -0
  5. django_cfg/apps/payments/admin/api_keys_admin.py +347 -0
  6. django_cfg/apps/payments/admin/balance_admin.py +434 -0
  7. django_cfg/apps/payments/admin/currencies_admin.py +186 -0
  8. django_cfg/apps/payments/admin/filters.py +259 -0
  9. django_cfg/apps/payments/admin/payments_admin.py +142 -0
  10. django_cfg/apps/payments/admin/subscriptions_admin.py +227 -0
  11. django_cfg/apps/payments/admin/tariffs_admin.py +199 -0
  12. django_cfg/apps/payments/apps.py +22 -0
  13. django_cfg/apps/payments/config/__init__.py +87 -0
  14. django_cfg/apps/payments/config/module.py +162 -0
  15. django_cfg/apps/payments/config/providers.py +93 -0
  16. django_cfg/apps/payments/config/settings.py +136 -0
  17. django_cfg/apps/payments/config/utils.py +198 -0
  18. django_cfg/apps/payments/decorators.py +291 -0
  19. django_cfg/apps/payments/managers/__init__.py +22 -0
  20. django_cfg/apps/payments/managers/api_key_manager.py +35 -0
  21. django_cfg/apps/payments/managers/balance_manager.py +361 -0
  22. django_cfg/apps/payments/managers/currency_manager.py +32 -0
  23. django_cfg/apps/payments/managers/payment_manager.py +44 -0
  24. django_cfg/apps/payments/managers/subscription_manager.py +37 -0
  25. django_cfg/apps/payments/managers/tariff_manager.py +29 -0
  26. django_cfg/apps/payments/middleware/__init__.py +13 -0
  27. django_cfg/apps/payments/middleware/api_access.py +261 -0
  28. django_cfg/apps/payments/middleware/rate_limiting.py +216 -0
  29. django_cfg/apps/payments/middleware/usage_tracking.py +296 -0
  30. django_cfg/apps/payments/migrations/0001_initial.py +1003 -0
  31. django_cfg/apps/payments/migrations/__init__.py +1 -0
  32. django_cfg/apps/payments/models/__init__.py +67 -0
  33. django_cfg/apps/payments/models/api_keys.py +96 -0
  34. django_cfg/apps/payments/models/balance.py +209 -0
  35. django_cfg/apps/payments/models/base.py +30 -0
  36. django_cfg/apps/payments/models/currencies.py +138 -0
  37. django_cfg/apps/payments/models/events.py +73 -0
  38. django_cfg/apps/payments/models/payments.py +301 -0
  39. django_cfg/apps/payments/models/subscriptions.py +270 -0
  40. django_cfg/apps/payments/models/tariffs.py +102 -0
  41. django_cfg/apps/payments/serializers/__init__.py +56 -0
  42. django_cfg/apps/payments/serializers/api_keys.py +51 -0
  43. django_cfg/apps/payments/serializers/balance.py +59 -0
  44. django_cfg/apps/payments/serializers/currencies.py +55 -0
  45. django_cfg/apps/payments/serializers/payments.py +62 -0
  46. django_cfg/apps/payments/serializers/subscriptions.py +71 -0
  47. django_cfg/apps/payments/serializers/tariffs.py +56 -0
  48. django_cfg/apps/payments/services/__init__.py +65 -0
  49. django_cfg/apps/payments/services/billing/__init__.py +8 -0
  50. django_cfg/apps/payments/services/cache/__init__.py +15 -0
  51. django_cfg/apps/payments/services/cache/base.py +30 -0
  52. django_cfg/apps/payments/services/cache/simple_cache.py +135 -0
  53. django_cfg/apps/payments/services/core/__init__.py +17 -0
  54. django_cfg/apps/payments/services/core/balance_service.py +449 -0
  55. django_cfg/apps/payments/services/core/payment_service.py +393 -0
  56. django_cfg/apps/payments/services/core/subscription_service.py +616 -0
  57. django_cfg/apps/payments/services/internal_types.py +266 -0
  58. django_cfg/apps/payments/services/middleware/__init__.py +8 -0
  59. django_cfg/apps/payments/services/providers/__init__.py +19 -0
  60. django_cfg/apps/payments/services/providers/base.py +137 -0
  61. django_cfg/apps/payments/services/providers/cryptapi.py +262 -0
  62. django_cfg/apps/payments/services/providers/nowpayments.py +293 -0
  63. django_cfg/apps/payments/services/providers/registry.py +99 -0
  64. django_cfg/apps/payments/services/validators/__init__.py +8 -0
  65. django_cfg/apps/payments/signals/__init__.py +13 -0
  66. django_cfg/apps/payments/signals/api_key_signals.py +150 -0
  67. django_cfg/apps/payments/signals/payment_signals.py +127 -0
  68. django_cfg/apps/payments/signals/subscription_signals.py +196 -0
  69. django_cfg/apps/payments/urls.py +78 -0
  70. django_cfg/apps/payments/utils/__init__.py +42 -0
  71. django_cfg/apps/payments/utils/config_utils.py +243 -0
  72. django_cfg/apps/payments/utils/middleware_utils.py +228 -0
  73. django_cfg/apps/payments/utils/validation_utils.py +94 -0
  74. django_cfg/apps/payments/views/__init__.py +62 -0
  75. django_cfg/apps/payments/views/api_key_views.py +164 -0
  76. django_cfg/apps/payments/views/balance_views.py +75 -0
  77. django_cfg/apps/payments/views/currency_views.py +111 -0
  78. django_cfg/apps/payments/views/payment_views.py +111 -0
  79. django_cfg/apps/payments/views/subscription_views.py +135 -0
  80. django_cfg/apps/payments/views/tariff_views.py +131 -0
  81. django_cfg/apps/support/signals.py +16 -4
  82. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  83. django_cfg/core/config.py +6 -0
  84. django_cfg/models/revolution.py +14 -0
  85. django_cfg/modules/base.py +9 -0
  86. django_cfg/modules/django_email.py +42 -4
  87. django_cfg/modules/django_unfold/dashboard.py +20 -0
  88. {django_cfg-1.2.21.dist-info → django_cfg-1.2.23.dist-info}/METADATA +2 -1
  89. {django_cfg-1.2.21.dist-info → django_cfg-1.2.23.dist-info}/RECORD +92 -14
  90. {django_cfg-1.2.21.dist-info → django_cfg-1.2.23.dist-info}/WHEEL +0 -0
  91. {django_cfg-1.2.21.dist-info → django_cfg-1.2.23.dist-info}/entry_points.txt +0 -0
  92. {django_cfg-1.2.21.dist-info → django_cfg-1.2.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,56 @@
1
+ """
2
+ DRF serializers for the universal payments system.
3
+ """
4
+
5
+ from .balance import (
6
+ UserBalanceSerializer, TransactionSerializer, TransactionListSerializer
7
+ )
8
+ from .payments import (
9
+ UniversalPaymentSerializer, PaymentCreateSerializer, PaymentListSerializer
10
+ )
11
+ from .subscriptions import (
12
+ SubscriptionSerializer, SubscriptionCreateSerializer, SubscriptionListSerializer,
13
+ EndpointGroupSerializer
14
+ )
15
+ from .api_keys import (
16
+ APIKeySerializer, APIKeyCreateSerializer, APIKeyListSerializer
17
+ )
18
+ from .currencies import (
19
+ CurrencySerializer, CurrencyNetworkSerializer, CurrencyListSerializer
20
+ )
21
+ from .tariffs import (
22
+ TariffSerializer, TariffEndpointGroupSerializer, TariffListSerializer
23
+ )
24
+
25
+ __all__ = [
26
+ # Balance
27
+ 'UserBalanceSerializer',
28
+ 'TransactionSerializer',
29
+ 'TransactionListSerializer',
30
+
31
+ # Payments
32
+ 'UniversalPaymentSerializer',
33
+ 'PaymentCreateSerializer',
34
+ 'PaymentListSerializer',
35
+
36
+ # Subscriptions
37
+ 'SubscriptionSerializer',
38
+ 'SubscriptionCreateSerializer',
39
+ 'SubscriptionListSerializer',
40
+ 'EndpointGroupSerializer',
41
+
42
+ # API Keys
43
+ 'APIKeySerializer',
44
+ 'APIKeyCreateSerializer',
45
+ 'APIKeyListSerializer',
46
+
47
+ # Currencies
48
+ 'CurrencySerializer',
49
+ 'CurrencyNetworkSerializer',
50
+ 'CurrencyListSerializer',
51
+
52
+ # Tariffs
53
+ 'TariffSerializer',
54
+ 'TariffEndpointGroupSerializer',
55
+ 'TariffListSerializer',
56
+ ]
@@ -0,0 +1,51 @@
1
+ """
2
+ API key serializers.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+ from ..models import APIKey
7
+
8
+
9
+ class APIKeySerializer(serializers.ModelSerializer):
10
+ """API key with usage stats."""
11
+
12
+ is_valid = serializers.SerializerMethodField()
13
+
14
+ class Meta:
15
+ model = APIKey
16
+ fields = [
17
+ 'id', 'name', 'key_value', 'key_prefix', 'usage_count',
18
+ 'is_active', 'is_valid', 'last_used', 'expires_at',
19
+ 'created_at'
20
+ ]
21
+ read_only_fields = ['key_value', 'key_prefix', 'usage_count', 'last_used', 'created_at']
22
+
23
+ def get_is_valid(self, obj):
24
+ """Get validation status."""
25
+ return obj.is_valid()
26
+
27
+
28
+ class APIKeyCreateSerializer(serializers.ModelSerializer):
29
+ """Create API key."""
30
+
31
+ class Meta:
32
+ model = APIKey
33
+ fields = ['name', 'expires_at']
34
+
35
+
36
+ class APIKeyListSerializer(serializers.ModelSerializer):
37
+ """Simplified API key for lists."""
38
+
39
+ is_valid = serializers.SerializerMethodField()
40
+
41
+ class Meta:
42
+ model = APIKey
43
+ fields = [
44
+ 'id', 'name', 'key_prefix', 'usage_count', 'is_active', 'is_valid',
45
+ 'last_used', 'expires_at', 'created_at'
46
+ ]
47
+ read_only_fields = ['key_prefix', 'usage_count', 'last_used', 'created_at']
48
+
49
+ def get_is_valid(self, obj):
50
+ """Get validation status."""
51
+ return obj.is_valid()
@@ -0,0 +1,59 @@
1
+ """
2
+ Balance serializers.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+ from ..models import UserBalance, Transaction
7
+
8
+
9
+ class UserBalanceSerializer(serializers.ModelSerializer):
10
+ """User balance with computed fields."""
11
+
12
+ total_balance = serializers.ReadOnlyField()
13
+ pending_payments_count = serializers.SerializerMethodField()
14
+
15
+ class Meta:
16
+ model = UserBalance
17
+ fields = [
18
+ 'amount_usd', 'reserved_usd', 'total_balance',
19
+ 'total_earned', 'total_spent', 'last_transaction_at',
20
+ 'pending_payments_count', 'created_at', 'updated_at'
21
+ ]
22
+ read_only_fields = [
23
+ 'total_earned', 'total_spent', 'last_transaction_at',
24
+ 'created_at', 'updated_at'
25
+ ]
26
+
27
+ def get_pending_payments_count(self, obj):
28
+ """Get count of pending payments."""
29
+ return obj.user.universal_payments.filter(status='pending').count()
30
+
31
+
32
+ class TransactionSerializer(serializers.ModelSerializer):
33
+ """Transaction with details."""
34
+
35
+ transaction_type_display = serializers.CharField(source='get_transaction_type_display', read_only=True)
36
+ is_credit = serializers.ReadOnlyField()
37
+ is_debit = serializers.ReadOnlyField()
38
+
39
+ class Meta:
40
+ model = Transaction
41
+ fields = [
42
+ 'id', 'amount_usd', 'transaction_type', 'transaction_type_display',
43
+ 'description', 'balance_before', 'balance_after',
44
+ 'is_credit', 'is_debit', 'reference_id', 'created_at'
45
+ ]
46
+ read_only_fields = ['id', 'balance_before', 'balance_after', 'created_at']
47
+
48
+
49
+ class TransactionListSerializer(serializers.ModelSerializer):
50
+ """Simplified transaction for lists."""
51
+
52
+ transaction_type_display = serializers.CharField(source='get_transaction_type_display', read_only=True)
53
+
54
+ class Meta:
55
+ model = Transaction
56
+ fields = [
57
+ 'id', 'amount_usd', 'transaction_type', 'transaction_type_display',
58
+ 'description', 'balance_after', 'created_at'
59
+ ]
@@ -0,0 +1,55 @@
1
+ """
2
+ Currency serializers.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+ from ..models import Currency, CurrencyNetwork
7
+
8
+
9
+ class CurrencySerializer(serializers.ModelSerializer):
10
+ """Currency with type info."""
11
+
12
+ currency_type_display = serializers.CharField(source='get_currency_type_display', read_only=True)
13
+ is_crypto = serializers.SerializerMethodField()
14
+ is_fiat = serializers.SerializerMethodField()
15
+
16
+ class Meta:
17
+ model = Currency
18
+ fields = [
19
+ 'id', 'code', 'name', 'symbol', 'currency_type', 'currency_type_display',
20
+ 'is_crypto', 'is_fiat', 'decimal_places', 'usd_rate', 'rate_updated_at',
21
+ 'is_active', 'min_payment_amount'
22
+ ]
23
+ read_only_fields = ['rate_updated_at']
24
+
25
+ def get_is_crypto(self, obj):
26
+ """Check if currency is crypto."""
27
+ return obj.is_crypto
28
+
29
+ def get_is_fiat(self, obj):
30
+ """Check if currency is fiat."""
31
+ return obj.is_fiat
32
+
33
+
34
+ class CurrencyNetworkSerializer(serializers.ModelSerializer):
35
+ """Currency network with status."""
36
+
37
+ currency_code = serializers.CharField(source='currency.code', read_only=True)
38
+ currency_name = serializers.CharField(source='currency.name', read_only=True)
39
+
40
+ class Meta:
41
+ model = CurrencyNetwork
42
+ fields = [
43
+ 'id', 'currency', 'currency_code', 'currency_name', 'network_code',
44
+ 'network_name', 'is_active', 'confirmation_blocks'
45
+ ]
46
+
47
+
48
+ class CurrencyListSerializer(serializers.ModelSerializer):
49
+ """Simplified currency for lists."""
50
+
51
+ currency_type_display = serializers.CharField(source='get_currency_type_display', read_only=True)
52
+
53
+ class Meta:
54
+ model = Currency
55
+ fields = ['id', 'code', 'name', 'currency_type', 'currency_type_display', 'is_active']
@@ -0,0 +1,62 @@
1
+ """
2
+ Payment serializers.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+ from ..models import UniversalPayment
7
+
8
+
9
+ class UniversalPaymentSerializer(serializers.ModelSerializer):
10
+ """Universal payment with status info."""
11
+
12
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
13
+ provider_display = serializers.CharField(source='get_provider_display', read_only=True)
14
+ is_pending = serializers.ReadOnlyField()
15
+ is_completed = serializers.ReadOnlyField()
16
+ is_failed = serializers.ReadOnlyField()
17
+
18
+ class Meta:
19
+ model = UniversalPayment
20
+ fields = [
21
+ 'id', 'internal_payment_id', 'provider_payment_id', 'order_id',
22
+ 'amount_usd', 'currency_code', 'actual_amount_usd', 'actual_currency_code',
23
+ 'fee_amount_usd', 'provider', 'provider_display', 'status', 'status_display',
24
+ 'pay_address', 'pay_amount', 'network', 'description',
25
+ 'is_pending', 'is_completed', 'is_failed',
26
+ 'expires_at', 'completed_at', 'created_at', 'updated_at'
27
+ ]
28
+ read_only_fields = [
29
+ 'id', 'internal_payment_id', 'provider_payment_id',
30
+ 'actual_amount_usd', 'actual_currency_code', 'fee_amount_usd',
31
+ 'pay_address', 'pay_amount', 'completed_at', 'processed_at',
32
+ 'created_at', 'updated_at'
33
+ ]
34
+
35
+
36
+ class PaymentCreateSerializer(serializers.ModelSerializer):
37
+ """Create payment request."""
38
+
39
+ class Meta:
40
+ model = UniversalPayment
41
+ fields = [
42
+ 'amount_usd', 'currency_code', 'provider', 'description', 'order_id'
43
+ ]
44
+
45
+ def validate_amount_usd(self, value):
46
+ """Validate payment amount."""
47
+ if value < 1.0:
48
+ raise serializers.ValidationError("Minimum payment amount is $1.00")
49
+ return value
50
+
51
+
52
+ class PaymentListSerializer(serializers.ModelSerializer):
53
+ """Simplified payment for lists."""
54
+
55
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
56
+
57
+ class Meta:
58
+ model = UniversalPayment
59
+ fields = [
60
+ 'id', 'internal_payment_id', 'amount_usd', 'currency_code',
61
+ 'provider', 'status', 'status_display', 'description', 'created_at'
62
+ ]
@@ -0,0 +1,71 @@
1
+ """
2
+ Subscription serializers.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+ from ..models import Subscription, EndpointGroup
7
+
8
+
9
+ class EndpointGroupSerializer(serializers.ModelSerializer):
10
+ """Endpoint group with pricing tiers."""
11
+
12
+ class Meta:
13
+ model = EndpointGroup
14
+ fields = [
15
+ 'id', 'name', 'display_name', 'description',
16
+ 'basic_price', 'premium_price', 'enterprise_price',
17
+ 'basic_limit', 'premium_limit', 'enterprise_limit',
18
+ 'is_active', 'require_api_key'
19
+ ]
20
+
21
+
22
+ class SubscriptionSerializer(serializers.ModelSerializer):
23
+ """Subscription with computed fields."""
24
+
25
+ endpoint_group_name = serializers.CharField(source='endpoint_group.name', read_only=True)
26
+ endpoint_group_display = serializers.CharField(source='endpoint_group.display_name', read_only=True)
27
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
28
+ tier_display = serializers.CharField(source='get_tier_display', read_only=True)
29
+ is_active_subscription = serializers.ReadOnlyField(source='is_active')
30
+ is_usage_exceeded = serializers.ReadOnlyField()
31
+
32
+ class Meta:
33
+ model = Subscription
34
+ fields = [
35
+ 'id', 'endpoint_group', 'endpoint_group_name', 'endpoint_group_display',
36
+ 'tier', 'tier_display', 'status', 'status_display', 'monthly_price',
37
+ 'usage_limit', 'usage_current', 'is_active_subscription', 'is_usage_exceeded',
38
+ 'last_billed', 'next_billing', 'expires_at', 'created_at'
39
+ ]
40
+ read_only_fields = [
41
+ 'usage_current', 'last_billed', 'next_billing', 'cancelled_at', 'created_at'
42
+ ]
43
+
44
+
45
+ class SubscriptionCreateSerializer(serializers.Serializer):
46
+ """Create subscription request."""
47
+
48
+ endpoint_group_id = serializers.IntegerField()
49
+ tier = serializers.ChoiceField(choices=Subscription.SubscriptionTier.choices)
50
+
51
+ def validate_endpoint_group_id(self, value):
52
+ """Validate endpoint group exists."""
53
+ try:
54
+ endpoint_group = EndpointGroup.objects.get(id=value, is_active=True)
55
+ return value
56
+ except EndpointGroup.DoesNotExist:
57
+ raise serializers.ValidationError("Endpoint group not found or inactive")
58
+
59
+
60
+ class SubscriptionListSerializer(serializers.ModelSerializer):
61
+ """Simplified subscription for lists."""
62
+
63
+ endpoint_group_name = serializers.CharField(source='endpoint_group.name', read_only=True)
64
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
65
+
66
+ class Meta:
67
+ model = Subscription
68
+ fields = [
69
+ 'id', 'endpoint_group_name', 'tier', 'status', 'status_display',
70
+ 'monthly_price', 'usage_current', 'usage_limit', 'expires_at'
71
+ ]
@@ -0,0 +1,56 @@
1
+ """
2
+ Tariff serializers.
3
+ """
4
+
5
+ from rest_framework import serializers
6
+ from ..models import Tariff, TariffEndpointGroup
7
+
8
+
9
+ class TariffSerializer(serializers.ModelSerializer):
10
+ """Tariff with pricing info."""
11
+
12
+ is_free = serializers.SerializerMethodField()
13
+ endpoint_groups_count = serializers.SerializerMethodField()
14
+
15
+ class Meta:
16
+ model = Tariff
17
+ fields = [
18
+ 'id', 'name', 'display_name', 'description', 'monthly_price',
19
+ 'request_limit', 'is_free', 'is_active', 'endpoint_groups_count'
20
+ ]
21
+
22
+ def get_is_free(self, obj):
23
+ """Check if tariff is free."""
24
+ return obj.is_free
25
+
26
+ def get_endpoint_groups_count(self, obj):
27
+ """Get count of enabled endpoint groups."""
28
+ return obj.endpoint_groups.filter(is_enabled=True).count()
29
+
30
+
31
+ class TariffEndpointGroupSerializer(serializers.ModelSerializer):
32
+ """Tariff endpoint group association."""
33
+
34
+ tariff_name = serializers.CharField(source='tariff.name', read_only=True)
35
+ endpoint_group_name = serializers.CharField(source='endpoint_group.name', read_only=True)
36
+
37
+ class Meta:
38
+ model = TariffEndpointGroup
39
+ fields = [
40
+ 'id', 'tariff', 'tariff_name', 'endpoint_group', 'endpoint_group_name',
41
+ 'is_enabled'
42
+ ]
43
+
44
+
45
+ class TariffListSerializer(serializers.ModelSerializer):
46
+ """Simplified tariff for lists."""
47
+
48
+ is_free = serializers.SerializerMethodField()
49
+
50
+ class Meta:
51
+ model = Tariff
52
+ fields = ['id', 'name', 'display_name', 'monthly_price', 'is_free', 'is_active']
53
+
54
+ def get_is_free(self, obj):
55
+ """Check if tariff is free."""
56
+ return obj.is_free
@@ -0,0 +1,65 @@
1
+ """
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.
6
+ """
7
+
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
+ )
30
+
31
+ __all__ = [
32
+ # Core services
33
+ 'PaymentService',
34
+ 'BalanceService',
35
+ 'SubscriptionService',
36
+
37
+ # Provider services
38
+ 'ProviderRegistry',
39
+ 'NowPaymentsProvider',
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',
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
@@ -0,0 +1,135 @@
1
+ """
2
+ Simple cache implementation for API keys and rate limiting.
3
+ ONLY for API access control - NOT payment data!
4
+ """
5
+
6
+ import logging
7
+ from typing import Optional, Any
8
+ from django.core.cache import cache
9
+
10
+ from .base import CacheInterface
11
+ from ...utils.config_utils import CacheConfigHelper
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SimpleCache(CacheInterface):
17
+ """
18
+ Simple cache implementation using Django's cache framework.
19
+
20
+ Falls back gracefully when cache is unavailable.
21
+ """
22
+
23
+ def __init__(self, prefix: str = "payments"):
24
+ self.prefix = prefix
25
+ # Use config helper to check if cache is enabled
26
+ self.enabled = CacheConfigHelper.is_cache_enabled()
27
+
28
+ def _make_key(self, key: str) -> str:
29
+ """Create prefixed cache key."""
30
+ return f"{self.prefix}:{key}"
31
+
32
+ def get(self, key: str) -> Optional[Any]:
33
+ """Get value from cache."""
34
+ if not self.enabled:
35
+ return None
36
+
37
+ try:
38
+ cache_key = self._make_key(key)
39
+ return cache.get(cache_key)
40
+ except Exception as e:
41
+ logger.warning(f"Cache get failed for key {key}: {e}")
42
+ return None
43
+
44
+ def set(self, key: str, value: Any, timeout: Optional[int] = None) -> bool:
45
+ """Set value in cache."""
46
+ if not self.enabled:
47
+ return False
48
+
49
+ try:
50
+ cache_key = self._make_key(key)
51
+ cache.set(cache_key, value, timeout)
52
+ return True
53
+ except Exception as e:
54
+ logger.warning(f"Cache set failed for key {key}: {e}")
55
+ return False
56
+
57
+ def delete(self, key: str) -> bool:
58
+ """Delete value from cache."""
59
+ if not self.enabled:
60
+ return False
61
+
62
+ try:
63
+ cache_key = self._make_key(key)
64
+ cache.delete(cache_key)
65
+ return True
66
+ except Exception as e:
67
+ logger.warning(f"Cache delete failed for key {key}: {e}")
68
+ return False
69
+
70
+ def exists(self, key: str) -> bool:
71
+ """Check if key exists in cache."""
72
+ if not self.enabled:
73
+ return False
74
+
75
+ try:
76
+ cache_key = self._make_key(key)
77
+ return cache.get(cache_key) is not None
78
+ except Exception as e:
79
+ logger.warning(f"Cache exists check failed for key {key}: {e}")
80
+ return False
81
+
82
+
83
+ class ApiKeyCache:
84
+ """Specialized cache for API key operations."""
85
+
86
+ def __init__(self):
87
+ self.cache = SimpleCache("api_keys")
88
+ # Get timeout from config
89
+ self.default_timeout = CacheConfigHelper.get_cache_timeout('api_key')
90
+
91
+ def get_api_key_data(self, api_key: str) -> Optional[dict]:
92
+ """Get cached API key data."""
93
+ return self.cache.get(f"key:{api_key}")
94
+
95
+ def cache_api_key_data(self, api_key: str, data: dict) -> bool:
96
+ """Cache API key data."""
97
+ return self.cache.set(f"key:{api_key}", data, self.default_timeout)
98
+
99
+ def invalidate_api_key(self, api_key: str) -> bool:
100
+ """Invalidate cached API key."""
101
+ return self.cache.delete(f"key:{api_key}")
102
+
103
+
104
+ class RateLimitCache:
105
+ """Specialized cache for rate limiting."""
106
+
107
+ def __init__(self):
108
+ self.cache = SimpleCache("rate_limit")
109
+
110
+ def get_usage_count(self, user_id: int, endpoint_group: str, window: str = "hour") -> int:
111
+ """Get current usage count for rate limiting."""
112
+ key = f"usage:{user_id}:{endpoint_group}:{window}"
113
+ count = self.cache.get(key)
114
+ return count if count is not None else 0
115
+
116
+ def increment_usage(self, user_id: int, endpoint_group: str, window: str = "hour", ttl: Optional[int] = None) -> int:
117
+ """Increment usage count and return new count."""
118
+ key = f"usage:{user_id}:{endpoint_group}:{window}"
119
+
120
+ # Get current count
121
+ current = self.get_usage_count(user_id, endpoint_group, window)
122
+ new_count = current + 1
123
+
124
+ # Use config helper for TTL if not provided
125
+ if ttl is None:
126
+ ttl = CacheConfigHelper.get_cache_timeout('rate_limit')
127
+
128
+ # Set new count with TTL
129
+ self.cache.set(key, new_count, ttl)
130
+ return new_count
131
+
132
+ def reset_usage(self, user_id: int, endpoint_group: str, window: str = "hour") -> bool:
133
+ """Reset usage count."""
134
+ key = f"usage:{user_id}:{endpoint_group}:{window}"
135
+ return self.cache.delete(key)