django-cfg 1.2.29__py3-none-any.whl → 1.2.31__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/payments/admin/__init__.py +3 -2
- django_cfg/apps/payments/admin/balance_admin.py +18 -18
- django_cfg/apps/payments/admin/currencies_admin.py +319 -131
- django_cfg/apps/payments/admin/payments_admin.py +15 -4
- django_cfg/apps/payments/config/module.py +2 -2
- django_cfg/apps/payments/config/utils.py +2 -2
- django_cfg/apps/payments/decorators.py +2 -2
- django_cfg/apps/payments/management/commands/README.md +95 -127
- django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
- django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
- django_cfg/apps/payments/managers/__init__.py +3 -2
- django_cfg/apps/payments/managers/balance_manager.py +2 -2
- django_cfg/apps/payments/managers/currency_manager.py +272 -49
- django_cfg/apps/payments/managers/payment_manager.py +161 -13
- django_cfg/apps/payments/middleware/api_access.py +2 -2
- django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
- django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
- django_cfg/apps/payments/models/__init__.py +3 -2
- django_cfg/apps/payments/models/currencies.py +187 -71
- django_cfg/apps/payments/models/payments.py +3 -2
- django_cfg/apps/payments/serializers/__init__.py +3 -2
- django_cfg/apps/payments/serializers/currencies.py +20 -12
- django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
- django_cfg/apps/payments/services/core/balance_service.py +2 -2
- django_cfg/apps/payments/services/core/fallback_service.py +2 -2
- django_cfg/apps/payments/services/core/payment_service.py +3 -6
- django_cfg/apps/payments/services/core/subscription_service.py +4 -7
- django_cfg/apps/payments/services/internal_types.py +171 -7
- django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
- django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
- django_cfg/apps/payments/services/providers/base.py +144 -43
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
- django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
- django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
- django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
- django_cfg/apps/payments/services/providers/registry.py +294 -11
- django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
- django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
- django_cfg/apps/payments/services/security/error_handler.py +6 -8
- django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
- django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
- django_cfg/apps/payments/signals/api_key_signals.py +2 -2
- django_cfg/apps/payments/signals/payment_signals.py +11 -5
- django_cfg/apps/payments/signals/subscription_signals.py +2 -2
- django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
- django_cfg/apps/payments/templates/payments/base.html +4 -4
- django_cfg/apps/payments/templates/payments/components/payment_card.html +6 -6
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +4 -4
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +14 -7
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +2 -2
- django_cfg/apps/payments/templates/payments/components/status_badge.html +8 -1
- django_cfg/apps/payments/templates/payments/components/status_overview.html +34 -30
- django_cfg/apps/payments/templates/payments/dashboard.html +202 -290
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
- django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
- django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
- django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
- django_cfg/apps/payments/templates/payments/stats.html +261 -0
- django_cfg/apps/payments/templates/payments/test.html +213 -0
- django_cfg/apps/payments/urls.py +3 -1
- django_cfg/apps/payments/{urls_templates.py → urls_admin.py} +6 -0
- django_cfg/apps/payments/utils/__init__.py +1 -3
- django_cfg/apps/payments/utils/billing_utils.py +2 -2
- django_cfg/apps/payments/utils/config_utils.py +2 -8
- django_cfg/apps/payments/utils/validation_utils.py +2 -2
- django_cfg/apps/payments/views/__init__.py +3 -2
- django_cfg/apps/payments/views/currency_views.py +31 -20
- django_cfg/apps/payments/views/payment_views.py +2 -2
- django_cfg/apps/payments/views/templates/ajax.py +141 -2
- django_cfg/apps/payments/views/templates/base.py +21 -13
- django_cfg/apps/payments/views/templates/payment_detail.py +1 -1
- django_cfg/apps/payments/views/templates/payment_management.py +34 -40
- django_cfg/apps/payments/views/templates/stats.py +8 -4
- django_cfg/apps/payments/views/webhook_views.py +2 -2
- django_cfg/apps/payments/viewsets.py +3 -2
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +4 -4
- django_cfg/core/config.py +35 -0
- django_cfg/models/payments.py +2 -8
- django_cfg/modules/django_currency/__init__.py +16 -11
- django_cfg/modules/django_currency/clients/__init__.py +4 -4
- django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
- django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
- django_cfg/modules/django_currency/core/__init__.py +1 -7
- django_cfg/modules/django_currency/core/converter.py +18 -23
- django_cfg/modules/django_currency/core/models.py +122 -11
- django_cfg/modules/django_currency/database/__init__.py +4 -4
- django_cfg/modules/django_currency/database/database_loader.py +190 -309
- django_cfg/modules/django_unfold/dashboard.py +7 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/admin/components/action_grid.html +9 -9
- django_cfg/templates/admin/components/metric_card.html +5 -5
- django_cfg/templates/admin/components/status_badge.html +2 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
- django_cfg/templates/admin/snippets/components/system_health.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/METADATA +2 -4
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/RECORD +118 -96
- django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
- django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -5,20 +5,39 @@ Abstract base class for all payment providers.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
from abc import ABC, abstractmethod
|
8
|
-
from typing import Optional, List
|
8
|
+
from typing import Optional, List, Dict
|
9
|
+
from django.db.models import QuerySet
|
9
10
|
from decimal import Decimal
|
10
11
|
|
11
|
-
from
|
12
|
+
from django.db import models, transaction
|
13
|
+
from cachetools import TTLCache
|
12
14
|
|
15
|
+
from ..internal_types import ProviderResponse, WebhookData, PaymentAmountEstimate, ProviderInfo, UniversalCurrency, UniversalCurrenciesResponse, ProviderSyncResult
|
16
|
+
from ...models import Currency, Network, ProviderCurrency
|
17
|
+
from django_cfg.modules.django_logger import get_logger
|
18
|
+
|
19
|
+
|
20
|
+
logger = get_logger('base_provider')
|
13
21
|
|
14
22
|
class PaymentProvider(ABC):
|
15
23
|
"""Abstract base class for payment providers."""
|
16
24
|
|
17
|
-
|
25
|
+
# Class-level cache for all providers (5 min TTL)
|
26
|
+
_api_cache = TTLCache(maxsize=100, ttl=300)
|
27
|
+
|
28
|
+
def __init__(self, config):
|
18
29
|
"""Initialize provider with config."""
|
19
30
|
self.config = config
|
20
31
|
self.name = self.__class__.__name__.lower().replace('provider', '')
|
21
|
-
self.
|
32
|
+
self.logger = get_logger(f"payment.{self.name}")
|
33
|
+
|
34
|
+
# Handle both dict and Pydantic model configs
|
35
|
+
if hasattr(config, 'enabled'):
|
36
|
+
self.enabled = config.enabled
|
37
|
+
elif hasattr(config, 'get'):
|
38
|
+
self.enabled = config.get('enabled', True)
|
39
|
+
else:
|
40
|
+
self.enabled = getattr(config, 'enabled', True)
|
22
41
|
|
23
42
|
@abstractmethod
|
24
43
|
def create_payment(self, payment_data: dict) -> ProviderResponse:
|
@@ -61,16 +80,23 @@ class PaymentProvider(ABC):
|
|
61
80
|
"""
|
62
81
|
pass
|
63
82
|
|
64
|
-
@abstractmethod
|
65
|
-
def
|
83
|
+
@abstractmethod
|
84
|
+
def get_parsed_currencies(self) -> UniversalCurrenciesResponse:
|
66
85
|
"""
|
67
|
-
Get
|
86
|
+
Get parsed and normalized currencies ready for database sync.
|
87
|
+
|
88
|
+
This method should:
|
89
|
+
1. Fetch data from provider API
|
90
|
+
2. Parse provider codes into base_currency + network
|
91
|
+
3. Return universal format
|
68
92
|
|
69
93
|
Returns:
|
70
|
-
|
94
|
+
UniversalCurrenciesResponse with parsed data
|
71
95
|
"""
|
72
96
|
pass
|
73
97
|
|
98
|
+
|
99
|
+
|
74
100
|
def validate_webhook(self, payload: dict, headers: Optional[dict] = None) -> bool:
|
75
101
|
"""
|
76
102
|
Validate webhook signature and data.
|
@@ -85,33 +111,6 @@ class PaymentProvider(ABC):
|
|
85
111
|
# Default implementation - providers can override
|
86
112
|
return True
|
87
113
|
|
88
|
-
def get_minimum_payment_amount(self, currency_from: str, currency_to: str = 'usd') -> Optional[Decimal]:
|
89
|
-
"""
|
90
|
-
Get minimum payment amount for currency pair.
|
91
|
-
|
92
|
-
Args:
|
93
|
-
currency_from: Source currency
|
94
|
-
currency_to: Target currency
|
95
|
-
|
96
|
-
Returns:
|
97
|
-
Minimum payment amount or None if not supported
|
98
|
-
"""
|
99
|
-
# Optional method - providers can override
|
100
|
-
return None
|
101
|
-
|
102
|
-
def estimate_payment_amount(self, amount: Decimal, currency_code: str) -> Optional[dict]:
|
103
|
-
"""
|
104
|
-
Estimate payment amount in target currency.
|
105
|
-
|
106
|
-
Args:
|
107
|
-
amount: Amount to estimate
|
108
|
-
currency_code: Target currency
|
109
|
-
|
110
|
-
Returns:
|
111
|
-
Dict with estimation data or None if not supported
|
112
|
-
"""
|
113
|
-
# Optional method - providers can override
|
114
|
-
return None
|
115
114
|
|
116
115
|
def check_api_status(self) -> bool:
|
117
116
|
"""
|
@@ -127,11 +126,113 @@ class PaymentProvider(ABC):
|
|
127
126
|
"""Check if provider is enabled."""
|
128
127
|
return self.enabled
|
129
128
|
|
130
|
-
def
|
131
|
-
"""
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
129
|
+
def sync_currencies_to_db(self) -> ProviderSyncResult:
|
130
|
+
"""
|
131
|
+
Synchronize provider currencies with clean architecture.
|
132
|
+
Uses get_parsed_currencies() to get normalized data.
|
133
|
+
"""
|
134
|
+
result = ProviderSyncResult()
|
135
|
+
|
136
|
+
# Get parsed data from provider
|
137
|
+
try:
|
138
|
+
parsed_response = self.get_parsed_currencies()
|
139
|
+
except Exception as e:
|
140
|
+
result.errors.append(f"Failed to get parsed currencies: {str(e)}")
|
141
|
+
return result
|
142
|
+
|
143
|
+
logger.info(f"Processing {len(parsed_response.currencies)} currencies from {self.name}")
|
144
|
+
|
145
|
+
with transaction.atomic():
|
146
|
+
for universal_currency in parsed_response.currencies:
|
147
|
+
try:
|
148
|
+
# 1. Create/update base Currency using normalized manager
|
149
|
+
currency, created = Currency.objects.get_or_create_normalized(
|
150
|
+
code=universal_currency.base_currency_code,
|
151
|
+
defaults={
|
152
|
+
'name': universal_currency.name,
|
153
|
+
'currency_type': universal_currency.currency_type
|
154
|
+
}
|
155
|
+
)
|
156
|
+
|
157
|
+
if created:
|
158
|
+
result.currencies_created += 1
|
159
|
+
logger.debug(f"Created currency: {universal_currency.base_currency_code}")
|
160
|
+
else:
|
161
|
+
result.currencies_updated += 1
|
162
|
+
|
163
|
+
# 2. Create/update Network (if needed) using normalized manager
|
164
|
+
network = None
|
165
|
+
if universal_currency.network_code:
|
166
|
+
network, created = Network.objects.get_or_create_normalized(
|
167
|
+
code=universal_currency.network_code,
|
168
|
+
defaults={
|
169
|
+
'name': universal_currency.network_code.title()
|
170
|
+
}
|
171
|
+
)
|
172
|
+
|
173
|
+
if created:
|
174
|
+
result.networks_created += 1
|
175
|
+
logger.debug(f"Created network: {universal_currency.network_code}")
|
176
|
+
else:
|
177
|
+
result.networks_updated += 1
|
178
|
+
|
179
|
+
# 3. Create/update ProviderCurrency mapping
|
180
|
+
provider_currency, created = ProviderCurrency.objects.get_or_create(
|
181
|
+
provider_name=self.name,
|
182
|
+
provider_currency_code=universal_currency.provider_currency_code,
|
183
|
+
defaults={
|
184
|
+
'base_currency': currency,
|
185
|
+
'network': network,
|
186
|
+
'is_enabled': universal_currency.is_enabled,
|
187
|
+
'is_popular': universal_currency.is_popular,
|
188
|
+
'is_stable': universal_currency.is_stable,
|
189
|
+
'priority': universal_currency.priority,
|
190
|
+
'logo_url': universal_currency.logo_url,
|
191
|
+
'available_for_payment': universal_currency.available_for_payment,
|
192
|
+
'available_for_payout': universal_currency.available_for_payout,
|
193
|
+
'min_amount': universal_currency.min_amount,
|
194
|
+
'max_amount': universal_currency.max_amount,
|
195
|
+
'metadata': universal_currency.raw_data
|
196
|
+
}
|
197
|
+
)
|
198
|
+
|
199
|
+
if created:
|
200
|
+
result.provider_currencies_created += 1
|
201
|
+
logger.debug(f"Created provider currency: {universal_currency.provider_currency_code}")
|
202
|
+
else:
|
203
|
+
# Update existing record
|
204
|
+
provider_currency.is_enabled = universal_currency.is_enabled
|
205
|
+
provider_currency.is_popular = universal_currency.is_popular
|
206
|
+
provider_currency.is_stable = universal_currency.is_stable
|
207
|
+
provider_currency.priority = universal_currency.priority
|
208
|
+
provider_currency.logo_url = universal_currency.logo_url
|
209
|
+
provider_currency.save()
|
210
|
+
result.provider_currencies_updated += 1
|
211
|
+
|
212
|
+
except Exception as e:
|
213
|
+
error_msg = f"Error processing {universal_currency.provider_currency_code}: {str(e)}"
|
214
|
+
result.errors.append(error_msg)
|
215
|
+
logger.error(error_msg)
|
216
|
+
|
217
|
+
logger.info(f"Sync completed: {result}")
|
218
|
+
return result
|
219
|
+
|
220
|
+
def get_provider_info(self) -> ProviderInfo:
|
221
|
+
"""Get provider information using parsed currencies."""
|
222
|
+
try:
|
223
|
+
parsed_response = self.get_parsed_currencies()
|
224
|
+
supported_currencies = [c.base_currency_code for c in parsed_response.currencies]
|
225
|
+
except Exception:
|
226
|
+
supported_currencies = []
|
227
|
+
|
228
|
+
return ProviderInfo(
|
229
|
+
name=self.name,
|
230
|
+
display_name=self.name.title(),
|
231
|
+
supported_currencies=supported_currencies,
|
232
|
+
is_active=self.enabled and self.check_api_status(),
|
233
|
+
features={
|
234
|
+
'supports_networks': True,
|
235
|
+
'supports_webhooks': True,
|
236
|
+
'supports_refunds': True
|
237
|
+
}
|
238
|
+
)
|
@@ -0,0 +1,8 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
4
|
+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3FT0Ym8b3myVxhQW7ESuuu6lo
|
5
|
+
dGAsUJs4fq+Ey//jm27jQ7HHHDmP1YJO7XE7Jf/0DTEJgcw4EZhJFVwsk6d3+4fy
|
6
|
+
Bsn0tKeyGMiaE6cVkX0cy6Y85o8zgc/CwZKc0uw6d5siAo++xl2zl+RGMXCELQVE
|
7
|
+
ox7pp208zTvown577wIDAQAB
|
8
|
+
-----END PUBLIC KEY-----"""
|
@@ -0,0 +1,192 @@
|
|
1
|
+
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
2
|
+
from typing import Optional, List, Dict, Any
|
3
|
+
from decimal import Decimal
|
4
|
+
|
5
|
+
from ...internal_types import ProviderConfig
|
6
|
+
from .config import PUBLIC_KEY
|
7
|
+
|
8
|
+
class CryptAPIConfig(ProviderConfig):
|
9
|
+
"""CryptAPI provider configuration with Pydantic v2."""
|
10
|
+
|
11
|
+
own_address: str = Field(..., description="Your cryptocurrency address")
|
12
|
+
callback_url: Optional[str] = Field(default=None, description="Webhook callback URL")
|
13
|
+
convert_payments: bool = Field(default=True, description="Auto-convert payments")
|
14
|
+
multi_token: bool = Field(default=True, description="Support multi-token payments")
|
15
|
+
priority: str = Field(default='default', description="Transaction priority")
|
16
|
+
verify_signatures: bool = Field(default=True, description="Enable webhook signature verification")
|
17
|
+
|
18
|
+
# CryptAPI's official public key for signature verification
|
19
|
+
public_key: str = Field(
|
20
|
+
default=PUBLIC_KEY,
|
21
|
+
description="CryptAPI RSA public key for signature verification"
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
class CryptAPICurrency(BaseModel):
|
26
|
+
"""CryptAPI specific currency model."""
|
27
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
28
|
+
|
29
|
+
currency_code: str = Field(..., description="Currency symbol (e.g., BTC, ETH)")
|
30
|
+
name: str = Field(..., description="Full currency name")
|
31
|
+
minimum_transaction: Optional[Decimal] = Field(None, description="Minimum transaction amount")
|
32
|
+
maximum_transaction: Optional[Decimal] = Field(None, description="Maximum transaction amount")
|
33
|
+
fee_percent: Optional[Decimal] = Field(None, description="Fee percentage")
|
34
|
+
logo: Optional[str] = Field(None, description="Currency logo URL")
|
35
|
+
|
36
|
+
|
37
|
+
class CryptAPINetwork(BaseModel):
|
38
|
+
"""CryptAPI specific network model."""
|
39
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
40
|
+
|
41
|
+
currency: str = Field(..., description="Currency code this network belongs to")
|
42
|
+
network: str = Field(..., description="Network code (e.g., mainnet, testnet)")
|
43
|
+
name: str = Field(..., description="Network display name")
|
44
|
+
confirmations: int = Field(1, description="Required confirmations")
|
45
|
+
fee: Optional[Decimal] = Field(None, description="Network fee")
|
46
|
+
|
47
|
+
|
48
|
+
class CryptAPIPaymentRequest(BaseModel):
|
49
|
+
"""CryptAPI payment creation request."""
|
50
|
+
ticker: str = Field(..., description="Currency ticker")
|
51
|
+
callback: str = Field(..., description="Callback URL")
|
52
|
+
address: Optional[str] = Field(None, description="Destination address")
|
53
|
+
pending: bool = Field(False, description="Accept pending transactions")
|
54
|
+
confirmations: int = Field(1, description="Required confirmations")
|
55
|
+
email: Optional[str] = Field(None, description="Email for notifications")
|
56
|
+
post: int = Field(0, description="POST data format")
|
57
|
+
json: int = Field(1, description="JSON response format")
|
58
|
+
priority: Optional[str] = Field(None, description="Priority level")
|
59
|
+
multi_token: bool = Field(False, description="Multi-token support")
|
60
|
+
convert: int = Field(1, description="Convert amounts")
|
61
|
+
|
62
|
+
|
63
|
+
class CryptAPIPaymentResponse(BaseModel):
|
64
|
+
"""CryptAPI payment creation response."""
|
65
|
+
address_in: str = Field(..., description="Payment address")
|
66
|
+
address_out: Optional[str] = Field(None, description="Destination address")
|
67
|
+
callback_url: str = Field(..., description="Callback URL")
|
68
|
+
priority: Optional[str] = Field(None, description="Priority level")
|
69
|
+
minimum: Optional[Decimal] = Field(None, description="Minimum amount")
|
70
|
+
|
71
|
+
|
72
|
+
class CryptAPICallback(BaseModel):
|
73
|
+
"""CryptAPI webhook callback data according to official documentation."""
|
74
|
+
model_config = ConfigDict(validate_assignment=True, extra="allow") # Allow extra fields for custom params
|
75
|
+
|
76
|
+
# Required fields from documentation
|
77
|
+
uuid: Optional[str] = Field(None, description="Unique identifier for each payment transaction")
|
78
|
+
address_in: str = Field(..., description="CryptAPI-generated payment address")
|
79
|
+
address_out: str = Field(..., description="Your destination address(es)")
|
80
|
+
txid_in: str = Field(..., description="Transaction hash of customer's payment")
|
81
|
+
coin: str = Field(..., description="Cryptocurrency ticker")
|
82
|
+
price: Optional[Decimal] = Field(None, description="Cryptocurrency price in USD")
|
83
|
+
pending: Optional[int] = Field(0, description="1=pending webhook, 0=confirmed webhook")
|
84
|
+
|
85
|
+
# Confirmed webhook only fields
|
86
|
+
txid_out: Optional[str] = Field(None, description="CryptAPI's forwarding transaction hash")
|
87
|
+
confirmations: Optional[int] = Field(None, description="Number of blockchain confirmations")
|
88
|
+
value_coin: Optional[Decimal] = Field(None, description="Payment amount before fees")
|
89
|
+
value_forwarded_coin: Optional[Decimal] = Field(None, description="Amount forwarded after fees")
|
90
|
+
fee_coin: Optional[Decimal] = Field(None, description="CryptAPI service fee")
|
91
|
+
|
92
|
+
# Optional conversion fields (when convert=1)
|
93
|
+
value_coin_convert: Optional[str] = Field(None, description="JSON FIAT conversions of value_coin")
|
94
|
+
value_forwarded_coin_convert: Optional[str] = Field(None, description="JSON FIAT conversions of value_forwarded_coin")
|
95
|
+
|
96
|
+
@field_validator('pending')
|
97
|
+
@classmethod
|
98
|
+
def validate_pending(cls, v):
|
99
|
+
"""Validate pending field is 0 or 1."""
|
100
|
+
if v not in [0, 1]:
|
101
|
+
raise ValueError("pending must be 0 (confirmed) or 1 (pending)")
|
102
|
+
return v
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
class CryptAPIInfoResponse(BaseModel):
|
107
|
+
"""CryptAPI info endpoint response."""
|
108
|
+
ticker: str = Field(..., description="Currency ticker")
|
109
|
+
minimum_transaction: Decimal = Field(..., description="Minimum transaction amount")
|
110
|
+
maximum_transaction: Optional[Decimal] = Field(None, description="Maximum transaction amount")
|
111
|
+
fee_percent: Decimal = Field(..., description="Fee percentage")
|
112
|
+
network_fee: Decimal = Field(..., description="Network fee")
|
113
|
+
prices: Dict[str, Decimal] = Field(..., description="Price conversions")
|
114
|
+
|
115
|
+
|
116
|
+
class CryptAPIEstimateFeeResponse(BaseModel):
|
117
|
+
"""CryptAPI fee estimation response."""
|
118
|
+
estimated_cost: Decimal = Field(..., description="Estimated cost")
|
119
|
+
estimated_cost_currency: Dict[str, Decimal] = Field(..., description="Cost in different currencies")
|
120
|
+
|
121
|
+
|
122
|
+
class CryptAPIConvertResponse(BaseModel):
|
123
|
+
"""CryptAPI currency conversion response."""
|
124
|
+
value_coin: Decimal = Field(..., description="Value in cryptocurrency")
|
125
|
+
exchange_rate: Decimal = Field(..., description="Exchange rate used")
|
126
|
+
|
127
|
+
|
128
|
+
class CryptAPIQRCodeResponse(BaseModel):
|
129
|
+
"""CryptAPI QR code response."""
|
130
|
+
qr_code: str = Field(..., description="Base64 encoded QR code image")
|
131
|
+
payment_uri: str = Field(..., description="Payment URI for QR code")
|
132
|
+
|
133
|
+
|
134
|
+
class CryptAPILogsResponse(BaseModel):
|
135
|
+
"""CryptAPI logs response."""
|
136
|
+
callbacks: List[Dict[str, Any]] = Field(default_factory=list, description="Callback logs")
|
137
|
+
payments: List[Dict[str, Any]] = Field(default_factory=list, description="Payment logs")
|
138
|
+
|
139
|
+
|
140
|
+
class CryptAPISupportedCoinsResponse(BaseModel):
|
141
|
+
"""CryptAPI supported coins response."""
|
142
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
143
|
+
|
144
|
+
currencies: List[CryptAPICurrency] = Field(..., description="List of supported currencies")
|
145
|
+
|
146
|
+
|
147
|
+
# =============================================================================
|
148
|
+
# MONITORING & HEALTH CHECK MODELS
|
149
|
+
# =============================================================================
|
150
|
+
|
151
|
+
class CryptAPIInfoResponse(BaseModel):
|
152
|
+
"""CryptAPI /btc/info/ response schema for health checks."""
|
153
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
154
|
+
|
155
|
+
coin: str = Field(..., description="Cryptocurrency name")
|
156
|
+
logo: str = Field(..., description="Logo URL")
|
157
|
+
ticker: str = Field(..., description="Currency ticker")
|
158
|
+
minimum_transaction: int = Field(..., description="Minimum transaction in satoshis")
|
159
|
+
minimum_transaction_coin: str = Field(..., description="Minimum transaction in coin units")
|
160
|
+
minimum_fee: int = Field(..., description="Minimum fee in satoshis")
|
161
|
+
minimum_fee_coin: str = Field(..., description="Minimum fee in coin units")
|
162
|
+
fee_percent: str = Field(..., description="Fee percentage")
|
163
|
+
network_fee_estimation: str = Field(..., description="Network fee estimation")
|
164
|
+
status: str = Field(..., description="API status")
|
165
|
+
prices: Dict[str, str] = Field(..., description="Prices in various fiat currencies")
|
166
|
+
prices_updated: str = Field(..., description="Prices last updated timestamp")
|
167
|
+
|
168
|
+
@field_validator('status')
|
169
|
+
@classmethod
|
170
|
+
def validate_status(cls, v):
|
171
|
+
"""Validate that status is success."""
|
172
|
+
if v != 'success':
|
173
|
+
raise ValueError(f"Expected status 'success', got '{v}'")
|
174
|
+
return v
|
175
|
+
|
176
|
+
@field_validator('prices')
|
177
|
+
@classmethod
|
178
|
+
def validate_prices_not_empty(cls, v):
|
179
|
+
"""Validate that prices dict is not empty."""
|
180
|
+
if not v:
|
181
|
+
raise ValueError("Prices dictionary cannot be empty")
|
182
|
+
return v
|
183
|
+
|
184
|
+
def get_usd_price(self) -> Optional[Decimal]:
|
185
|
+
"""Get USD price as Decimal."""
|
186
|
+
usd_price = self.prices.get('USD')
|
187
|
+
if usd_price:
|
188
|
+
try:
|
189
|
+
return Decimal(usd_price)
|
190
|
+
except:
|
191
|
+
return None
|
192
|
+
return None
|