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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -10
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +526 -222
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +465 -70
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
- django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
- django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +13 -18
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +172 -148
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -285
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +346 -467
- django_cfg/apps/payments/services/core/subscription_service.py +425 -481
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +234 -174
- django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
- django_cfg/apps/payments/services/providers/registry.py +367 -301
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +210 -129
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +128 -103
- django_cfg/apps/payments/signals/subscription_signals.py +194 -142
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +45 -48
- django_cfg/apps/payments/urls_admin.py +33 -42
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +40 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -492
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +64 -16
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -146
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
- django_cfg/apps/payments/managers/__init__.py +0 -23
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -306
- django_cfg/apps/payments/managers/payment_manager.py +0 -192
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -57
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -63
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -461
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
- django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
- django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
- django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
- django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
- django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
- django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -635
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
- django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
- django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
- django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
- django_cfg/apps/payments/templates/payments/stats.html +0 -261
- django_cfg/apps/payments/templates/payments/test.html +0 -213
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/utils/__init__.py +0 -43
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -239
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -63
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -122
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -451
- django_cfg/apps/payments/views/templates/base.py +0 -212
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -158
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -244
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -66
- django_cfg/core/integration.py +0 -160
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,439 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
CryptAPI provider implementation.
|
3
|
-
|
4
|
-
Crypto payment provider using CryptAPI service.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import requests
|
8
|
-
import secrets
|
9
|
-
import string
|
10
|
-
import base64
|
11
|
-
from typing import Optional, List, Dict, Any
|
12
|
-
from decimal import Decimal
|
13
|
-
from cryptography.hazmat.primitives import hashes, serialization
|
14
|
-
from cryptography.hazmat.primitives.asymmetric import padding
|
15
|
-
from cryptography.hazmat.backends import default_backend
|
16
|
-
from ..base import PaymentProvider
|
17
|
-
from ...internal_types import ProviderResponse, WebhookData, PaymentAmountEstimate
|
18
|
-
from .models import CryptAPIConfig, CryptAPICallback
|
19
|
-
from django_cfg.modules.django_logger import get_logger
|
20
|
-
|
21
|
-
logger = get_logger("cryptapi")
|
22
|
-
|
23
|
-
|
24
|
-
class CryptAPIException(Exception):
|
25
|
-
"""CryptAPI specific exception."""
|
26
|
-
pass
|
27
|
-
|
28
|
-
|
29
|
-
class CryptAPIProvider(PaymentProvider):
|
30
|
-
"""CryptAPI cryptocurrency payment provider."""
|
31
|
-
|
32
|
-
CRYPTAPI_URL = 'https://api.cryptapi.io/'
|
33
|
-
|
34
|
-
def __init__(self, config: CryptAPIConfig):
|
35
|
-
"""Initialize CryptAPI provider."""
|
36
|
-
super().__init__(config)
|
37
|
-
self.config = config
|
38
|
-
self.own_address = config.own_address
|
39
|
-
self.callback_url = config.callback_url
|
40
|
-
self.convert_payments = config.convert_payments
|
41
|
-
self.multi_token = config.multi_token
|
42
|
-
self.priority = config.priority
|
43
|
-
|
44
|
-
def _make_request(self, coin: str, endpoint: str, params: Optional[dict] = None) -> Optional[dict]:
|
45
|
-
"""Make HTTP request to CryptAPI."""
|
46
|
-
try:
|
47
|
-
if coin:
|
48
|
-
coin = coin.replace('/', '_')
|
49
|
-
url = f"{self.CRYPTAPI_URL}{coin}/{endpoint}/"
|
50
|
-
else:
|
51
|
-
url = f"{self.CRYPTAPI_URL}{endpoint}/"
|
52
|
-
|
53
|
-
response = requests.get(url, params=params or {}, timeout=30)
|
54
|
-
response.raise_for_status()
|
55
|
-
|
56
|
-
result = response.json()
|
57
|
-
|
58
|
-
# Check for API errors
|
59
|
-
if 'error' in result:
|
60
|
-
logger.error(f"CryptAPI error: {result['error']}")
|
61
|
-
return None
|
62
|
-
|
63
|
-
return result
|
64
|
-
|
65
|
-
except requests.exceptions.RequestException as e:
|
66
|
-
logger.error(f"CryptAPI request failed: {e}")
|
67
|
-
return None
|
68
|
-
except Exception as e:
|
69
|
-
logger.error(f"Unexpected CryptAPI error: {e}")
|
70
|
-
return None
|
71
|
-
|
72
|
-
def create_payment(self, payment_data: dict) -> ProviderResponse:
|
73
|
-
"""Create payment address via CryptAPI with full parameter support according to documentation."""
|
74
|
-
try:
|
75
|
-
# Required parameters
|
76
|
-
amount = Decimal(str(payment_data['amount']))
|
77
|
-
currency = payment_data['currency'].lower()
|
78
|
-
order_id = payment_data.get('order_id', f'payment_{int(amount * 100)}')
|
79
|
-
|
80
|
-
# Build callback URL with custom parameters
|
81
|
-
callback_url = payment_data.get('callback_url', self.config.callback_url)
|
82
|
-
|
83
|
-
# Add custom parameters to callback URL for tracking
|
84
|
-
callback_params = []
|
85
|
-
if order_id:
|
86
|
-
callback_params.append(f'order_id={order_id}')
|
87
|
-
if 'user_id' in payment_data:
|
88
|
-
callback_params.append(f'user_id={payment_data["user_id"]}')
|
89
|
-
|
90
|
-
# Generate nonce for security
|
91
|
-
security_nonce = self._generate_nonce()
|
92
|
-
callback_params.append(f'nonce={security_nonce}')
|
93
|
-
|
94
|
-
# Build full callback URL
|
95
|
-
if callback_params:
|
96
|
-
separator = '&' if '?' in callback_url else '?'
|
97
|
-
callback_url = f"{callback_url}{separator}{'&'.join(callback_params)}"
|
98
|
-
|
99
|
-
# Prepare API parameters according to documentation
|
100
|
-
params = {
|
101
|
-
'address': payment_data.get('address', self.config.own_address),
|
102
|
-
'callback': callback_url,
|
103
|
-
}
|
104
|
-
|
105
|
-
# Optional parameters from documentation
|
106
|
-
if payment_data.get('pending', False) or self.config.convert_payments:
|
107
|
-
params['pending'] = 1
|
108
|
-
|
109
|
-
if 'confirmations' in payment_data:
|
110
|
-
params['confirmations'] = int(payment_data['confirmations'])
|
111
|
-
|
112
|
-
if payment_data.get('post', False):
|
113
|
-
params['post'] = 1
|
114
|
-
|
115
|
-
if payment_data.get('json', True):
|
116
|
-
params['json'] = 1
|
117
|
-
|
118
|
-
if self.config.priority and self.config.priority != 'default':
|
119
|
-
params['priority'] = self.config.priority
|
120
|
-
|
121
|
-
if self.config.multi_token:
|
122
|
-
params['multi_token'] = 1
|
123
|
-
|
124
|
-
if self.config.convert_payments:
|
125
|
-
params['convert'] = 1
|
126
|
-
|
127
|
-
# Handle multi-address splitting if provided
|
128
|
-
if 'addresses' in payment_data and isinstance(payment_data['addresses'], list):
|
129
|
-
# Format: percentage@address|percentage@address
|
130
|
-
address_parts = []
|
131
|
-
for addr_info in payment_data['addresses']:
|
132
|
-
if isinstance(addr_info, dict) and 'address' in addr_info and 'percentage' in addr_info:
|
133
|
-
address_parts.append(f"{addr_info['percentage']}@{addr_info['address']}")
|
134
|
-
if address_parts:
|
135
|
-
params['address'] = '|'.join(address_parts)
|
136
|
-
|
137
|
-
# Make API request using ticker/create endpoint
|
138
|
-
response = self._make_request(currency, 'create', params)
|
139
|
-
|
140
|
-
if response and 'address_in' in response:
|
141
|
-
return ProviderResponse(
|
142
|
-
success=True,
|
143
|
-
provider_payment_id=response['address_in'],
|
144
|
-
payment_url=None, # CryptAPI doesn't provide hosted payment pages
|
145
|
-
pay_address=response['address_in'],
|
146
|
-
amount=amount,
|
147
|
-
currency=currency.upper(),
|
148
|
-
status='pending',
|
149
|
-
data={
|
150
|
-
'callback_url': response.get('callback_url'),
|
151
|
-
'minimum_transaction_coin': response.get('minimum_transaction_coin'),
|
152
|
-
'priority': response.get('priority'),
|
153
|
-
'nonce': security_nonce
|
154
|
-
}
|
155
|
-
)
|
156
|
-
else:
|
157
|
-
# Standardized error message format
|
158
|
-
if response and 'error' in response:
|
159
|
-
error_msg = f"CryptAPI error: {response['error']}"
|
160
|
-
elif response:
|
161
|
-
error_msg = "CryptAPI error: Invalid response format"
|
162
|
-
else:
|
163
|
-
error_msg = "CryptAPI error: No response from API"
|
164
|
-
|
165
|
-
return ProviderResponse(
|
166
|
-
success=False,
|
167
|
-
error_message=error_msg
|
168
|
-
)
|
169
|
-
|
170
|
-
except Exception as e:
|
171
|
-
logger.error(f"CryptAPI create_payment error: {e}")
|
172
|
-
return ProviderResponse(
|
173
|
-
success=False,
|
174
|
-
error_message=str(e)
|
175
|
-
)
|
176
|
-
|
177
|
-
def check_payment_status(self, payment_id: str) -> ProviderResponse:
|
178
|
-
"""Check payment status via CryptAPI."""
|
179
|
-
try:
|
180
|
-
# For CryptAPI, payment_id is the address
|
181
|
-
# We need to check logs to see if payment was received
|
182
|
-
# This is a limitation of CryptAPI - no direct status check by address
|
183
|
-
|
184
|
-
# Return pending status as CryptAPI uses callbacks for status updates
|
185
|
-
return ProviderResponse(
|
186
|
-
success=True,
|
187
|
-
provider_payment_id=payment_id,
|
188
|
-
status='pending',
|
189
|
-
pay_address=payment_id,
|
190
|
-
amount=Decimal('0'), # Unknown without logs
|
191
|
-
currency='unknown'
|
192
|
-
)
|
193
|
-
|
194
|
-
except Exception as e:
|
195
|
-
logger.error(f"CryptAPI check_payment_status error: {e}")
|
196
|
-
return ProviderResponse(
|
197
|
-
success=False,
|
198
|
-
error_message=str(e)
|
199
|
-
)
|
200
|
-
|
201
|
-
def process_webhook(self, payload: dict) -> WebhookData:
|
202
|
-
"""Process CryptAPI webhook according to official documentation."""
|
203
|
-
try:
|
204
|
-
# Parse and validate webhook using Pydantic model
|
205
|
-
webhook = CryptAPICallback(**payload)
|
206
|
-
|
207
|
-
# Determine status based on pending flag
|
208
|
-
pending = webhook.pending if webhook.pending is not None else 0
|
209
|
-
if pending == 1:
|
210
|
-
status = 'pending'
|
211
|
-
elif pending == 0:
|
212
|
-
# Confirmed webhook - check confirmations if available
|
213
|
-
if webhook.confirmations is not None and webhook.confirmations >= 1:
|
214
|
-
status = 'completed'
|
215
|
-
else:
|
216
|
-
status = 'processing'
|
217
|
-
else:
|
218
|
-
status = 'unknown'
|
219
|
-
|
220
|
-
# Use value_coin for confirmed webhooks, or estimate for pending
|
221
|
-
pay_amount = webhook.value_coin if webhook.value_coin is not None else Decimal('0')
|
222
|
-
|
223
|
-
# Use UUID if available, otherwise fall back to address_in
|
224
|
-
payment_id = webhook.uuid if webhook.uuid else webhook.address_in
|
225
|
-
|
226
|
-
return WebhookData(
|
227
|
-
provider_payment_id=payment_id,
|
228
|
-
status=status,
|
229
|
-
pay_amount=pay_amount,
|
230
|
-
pay_currency=webhook.coin.lower(),
|
231
|
-
actually_paid=pay_amount,
|
232
|
-
order_id=payload.get('order_id'), # Custom parameter from callback URL
|
233
|
-
signature=webhook.txid_in # Transaction hash as signature
|
234
|
-
)
|
235
|
-
|
236
|
-
except Exception as e:
|
237
|
-
logger.error(f"CryptAPI webhook processing error: {e}")
|
238
|
-
raise
|
239
|
-
|
240
|
-
def get_logs(self, callback_url: str) -> Optional[dict]:
|
241
|
-
"""Get payment logs for a specific callback URL."""
|
242
|
-
try:
|
243
|
-
response = self._make_request('GET', 'logs', {
|
244
|
-
'callback': callback_url
|
245
|
-
})
|
246
|
-
|
247
|
-
if response:
|
248
|
-
return {
|
249
|
-
'success': True,
|
250
|
-
'logs': response,
|
251
|
-
'callback_url': callback_url
|
252
|
-
}
|
253
|
-
|
254
|
-
return None
|
255
|
-
|
256
|
-
except Exception as e:
|
257
|
-
logger.error(f"CryptAPI get_logs error: {e}")
|
258
|
-
return None
|
259
|
-
|
260
|
-
def generate_qr_code(self, address: str, amount: Optional[Decimal] = None,
|
261
|
-
size: int = 512) -> Optional[dict]:
|
262
|
-
"""Generate QR code for payment address."""
|
263
|
-
try:
|
264
|
-
params = {
|
265
|
-
'address': address,
|
266
|
-
'size': size
|
267
|
-
}
|
268
|
-
|
269
|
-
if amount:
|
270
|
-
params['value'] = float(amount)
|
271
|
-
|
272
|
-
response = self._make_request('GET', 'qrcode', params)
|
273
|
-
|
274
|
-
if response and 'qr_code' in response:
|
275
|
-
return {
|
276
|
-
'success': True,
|
277
|
-
'qr_code_url': response['qr_code'],
|
278
|
-
'address': address,
|
279
|
-
'amount': amount,
|
280
|
-
'size': size
|
281
|
-
}
|
282
|
-
|
283
|
-
return None
|
284
|
-
|
285
|
-
except Exception as e:
|
286
|
-
logger.error(f"CryptAPI generate_qr_code error: {e}")
|
287
|
-
return None
|
288
|
-
|
289
|
-
def get_supported_currencies(self) -> ProviderResponse:
|
290
|
-
"""Get list of supported currencies."""
|
291
|
-
try:
|
292
|
-
response = self._make_request('', 'info')
|
293
|
-
|
294
|
-
if response and isinstance(response, dict):
|
295
|
-
# CryptAPI returns a dict with coin info
|
296
|
-
currencies = list(response.keys())
|
297
|
-
return ProviderResponse(
|
298
|
-
success=True,
|
299
|
-
data={'currencies': [{'currency_code': c, 'name': c.upper()} for c in currencies]}
|
300
|
-
)
|
301
|
-
else:
|
302
|
-
return ProviderResponse(
|
303
|
-
success=False,
|
304
|
-
error_message="Invalid response from CryptAPI info endpoint"
|
305
|
-
)
|
306
|
-
|
307
|
-
except Exception as e:
|
308
|
-
logger.error(f"Error getting supported currencies: {e}")
|
309
|
-
return ProviderResponse(
|
310
|
-
success=False,
|
311
|
-
error_message=f"Failed to get currencies from CryptAPI: {str(e)}"
|
312
|
-
)
|
313
|
-
|
314
|
-
def get_minimum_payment_amount(self, currency_from: str, currency_to: str = 'usd') -> Optional[Decimal]:
|
315
|
-
"""Get minimum payment amount for currency."""
|
316
|
-
try:
|
317
|
-
response = self._make_request(currency_from.lower(), 'info')
|
318
|
-
|
319
|
-
if response and 'minimum_transaction' in response:
|
320
|
-
return Decimal(str(response['minimum_transaction']))
|
321
|
-
|
322
|
-
return None
|
323
|
-
|
324
|
-
except Exception as e:
|
325
|
-
logger.error(f"Error getting minimum amount: {e}")
|
326
|
-
return None
|
327
|
-
|
328
|
-
def estimate_payment_amount(self, amount: Decimal, currency_code: str) -> Optional[dict]:
|
329
|
-
"""Estimate payment amount - CryptAPI doesn't provide this."""
|
330
|
-
# CryptAPI doesn't have a direct estimation API
|
331
|
-
# Would need to use external price APIs
|
332
|
-
return None
|
333
|
-
|
334
|
-
def validate_webhook(self, payload: dict, headers: Optional[dict] = None, raw_body: Optional[bytes] = None) -> bool:
|
335
|
-
"""Validate CryptAPI webhook with RSA SHA256 signature verification."""
|
336
|
-
try:
|
337
|
-
# Skip signature verification if disabled
|
338
|
-
if not self.config.verify_signatures:
|
339
|
-
logger.warning("CryptAPI signature verification is disabled - using basic validation only")
|
340
|
-
return self._basic_webhook_validation(payload)
|
341
|
-
|
342
|
-
# Check for signature header
|
343
|
-
if not headers or 'x-ca-signature' not in headers:
|
344
|
-
logger.error("Missing x-ca-signature header in CryptAPI webhook")
|
345
|
-
return False
|
346
|
-
|
347
|
-
signature_b64 = headers['x-ca-signature']
|
348
|
-
if not signature_b64:
|
349
|
-
logger.error("Empty x-ca-signature header")
|
350
|
-
return False
|
351
|
-
|
352
|
-
# Verify the signature
|
353
|
-
if not self._verify_cryptapi_signature(payload, signature_b64, raw_body):
|
354
|
-
logger.error("CryptAPI webhook signature verification failed")
|
355
|
-
return False
|
356
|
-
|
357
|
-
# Signature verified - do basic validation
|
358
|
-
return self._basic_webhook_validation(payload)
|
359
|
-
|
360
|
-
except Exception as e:
|
361
|
-
logger.error(f"CryptAPI webhook validation error: {e}")
|
362
|
-
return False
|
363
|
-
|
364
|
-
def _basic_webhook_validation(self, payload: dict) -> bool:
|
365
|
-
"""Basic webhook payload validation."""
|
366
|
-
# Core required fields (UUID is optional for compatibility)
|
367
|
-
required_fields = ['address_in', 'address_out', 'txid_in', 'coin']
|
368
|
-
|
369
|
-
for field in required_fields:
|
370
|
-
if field not in payload:
|
371
|
-
logger.warning(f"Missing required field in CryptAPI webhook: {field}")
|
372
|
-
return False
|
373
|
-
|
374
|
-
# Validate coin format if present
|
375
|
-
if 'coin' in payload and not payload['coin']:
|
376
|
-
logger.warning("Empty coin field in CryptAPI webhook")
|
377
|
-
return False
|
378
|
-
|
379
|
-
return True
|
380
|
-
|
381
|
-
def _verify_cryptapi_signature(self, payload: dict, signature_b64: str, raw_body: Optional[bytes] = None) -> bool:
|
382
|
-
"""Verify CryptAPI RSA SHA256 signature according to documentation."""
|
383
|
-
try:
|
384
|
-
# Load public key
|
385
|
-
public_key = serialization.load_pem_public_key(
|
386
|
-
self.config.public_key.encode('utf-8'),
|
387
|
-
backend=default_backend()
|
388
|
-
)
|
389
|
-
|
390
|
-
# Decode signature
|
391
|
-
signature = base64.b64decode(signature_b64)
|
392
|
-
|
393
|
-
# Determine what data to verify
|
394
|
-
if raw_body:
|
395
|
-
# For POST requests - verify raw body
|
396
|
-
data_to_verify = raw_body
|
397
|
-
else:
|
398
|
-
# For GET requests - construct URL from payload
|
399
|
-
# This is a fallback if raw_body is not provided
|
400
|
-
params = "&".join([f"{k}={v}" for k, v in payload.items()])
|
401
|
-
data_to_verify = params.encode('utf-8')
|
402
|
-
|
403
|
-
# Verify signature using RSA SHA256
|
404
|
-
public_key.verify(
|
405
|
-
signature,
|
406
|
-
data_to_verify,
|
407
|
-
padding.PKCS1v15(),
|
408
|
-
hashes.SHA256()
|
409
|
-
)
|
410
|
-
|
411
|
-
return True
|
412
|
-
|
413
|
-
except Exception as e:
|
414
|
-
logger.error(f"CryptAPI signature verification error: {e}")
|
415
|
-
return False
|
416
|
-
|
417
|
-
def check_api_status(self) -> bool:
|
418
|
-
"""Check if CryptAPI is available."""
|
419
|
-
try:
|
420
|
-
response = self._make_request('', 'info')
|
421
|
-
return response is not None
|
422
|
-
except:
|
423
|
-
return False
|
424
|
-
|
425
|
-
def get_logs(self, callback_url: str) -> Optional[dict]:
|
426
|
-
"""Get payment logs for a callback URL."""
|
427
|
-
try:
|
428
|
-
params = {'callback': callback_url}
|
429
|
-
# Note: This would need a specific coin, but we don't know which one
|
430
|
-
# This is a limitation of the current implementation
|
431
|
-
return None
|
432
|
-
except Exception as e:
|
433
|
-
logger.error(f"Error getting logs: {e}")
|
434
|
-
return None
|
435
|
-
|
436
|
-
def _generate_nonce(self, length: int = 32) -> str:
|
437
|
-
"""Generate cryptographically secure nonce for replay attack protection."""
|
438
|
-
sequence = string.ascii_letters + string.digits
|
439
|
-
return ''.join([secrets.choice(sequence) for _ in range(length)])
|
@@ -1,176 +0,0 @@
|
|
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
|
-
|
7
|
-
|
8
|
-
class CryptomusConfig(ProviderConfig):
|
9
|
-
"""Cryptomus provider configuration with Pydantic v2."""
|
10
|
-
|
11
|
-
merchant_id: str = Field(..., description="Cryptomus merchant ID")
|
12
|
-
test_mode: bool = Field(default=False, description="Enable test mode")
|
13
|
-
callback_url: Optional[str] = Field(None, description="Default callback URL")
|
14
|
-
success_url: Optional[str] = Field(None, description="Success redirect URL")
|
15
|
-
cancel_url: Optional[str] = Field(None, description="Cancel redirect URL")
|
16
|
-
|
17
|
-
@field_validator('merchant_id')
|
18
|
-
@classmethod
|
19
|
-
def validate_merchant_id(cls, v: str) -> str:
|
20
|
-
if not v or not v.strip():
|
21
|
-
raise ValueError("Merchant ID is required")
|
22
|
-
return v.strip()
|
23
|
-
|
24
|
-
|
25
|
-
class CryptomusCurrency(BaseModel):
|
26
|
-
"""Cryptomus 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
|
-
network: Optional[str] = Field(None, description="Network code")
|
32
|
-
network_name: Optional[str] = Field(None, description="Network display name")
|
33
|
-
min_amount: Optional[Decimal] = Field(None, description="Minimum transaction amount")
|
34
|
-
max_amount: Optional[Decimal] = Field(None, description="Maximum transaction amount")
|
35
|
-
commission_percent: Optional[Decimal] = Field(None, description="Commission percentage")
|
36
|
-
is_available: bool = Field(True, description="Currency availability")
|
37
|
-
|
38
|
-
|
39
|
-
class CryptomusNetwork(BaseModel):
|
40
|
-
"""Cryptomus specific network model."""
|
41
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
42
|
-
|
43
|
-
code: str = Field(..., description="Network code")
|
44
|
-
name: str = Field(..., description="Network display name")
|
45
|
-
currency: str = Field(..., description="Currency this network belongs to")
|
46
|
-
min_amount: Optional[Decimal] = Field(None, description="Minimum amount for this network")
|
47
|
-
max_amount: Optional[Decimal] = Field(None, description="Maximum amount for this network")
|
48
|
-
commission_percent: Optional[Decimal] = Field(None, description="Commission percentage")
|
49
|
-
confirmations: int = Field(1, description="Required confirmations")
|
50
|
-
|
51
|
-
|
52
|
-
class CryptomusPaymentRequest(BaseModel):
|
53
|
-
"""Cryptomus payment creation request."""
|
54
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
55
|
-
|
56
|
-
amount: str = Field(..., description="Payment amount")
|
57
|
-
currency: str = Field(..., description="Payment currency")
|
58
|
-
order_id: str = Field(..., description="Unique order identifier")
|
59
|
-
url_callback: Optional[str] = Field(None, description="Callback URL")
|
60
|
-
url_return: Optional[str] = Field(None, description="Return URL")
|
61
|
-
url_success: Optional[str] = Field(None, description="Success URL")
|
62
|
-
is_payment_multiple: bool = Field(False, description="Allow multiple payments")
|
63
|
-
lifetime: int = Field(3600, description="Payment lifetime in seconds")
|
64
|
-
to_currency: Optional[str] = Field(None, description="Target cryptocurrency")
|
65
|
-
|
66
|
-
|
67
|
-
class CryptomusPaymentResponse(BaseModel):
|
68
|
-
"""Cryptomus payment creation response."""
|
69
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
70
|
-
|
71
|
-
state: int = Field(..., description="Response state (0 = success)")
|
72
|
-
message: Optional[str] = Field(None, description="Response message")
|
73
|
-
result: Optional[Dict[str, Any]] = Field(None, description="Payment result data")
|
74
|
-
|
75
|
-
|
76
|
-
class CryptomusPaymentInfo(BaseModel):
|
77
|
-
"""Cryptomus payment information."""
|
78
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
79
|
-
|
80
|
-
uuid: str = Field(..., description="Payment UUID")
|
81
|
-
order_id: str = Field(..., description="Order ID")
|
82
|
-
amount: str = Field(..., description="Payment amount")
|
83
|
-
currency: str = Field(..., description="Payment currency")
|
84
|
-
address: Optional[str] = Field(None, description="Payment address")
|
85
|
-
url: Optional[str] = Field(None, description="Payment URL")
|
86
|
-
static_qr: Optional[str] = Field(None, description="QR code data")
|
87
|
-
network: Optional[str] = Field(None, description="Network")
|
88
|
-
status: str = Field(..., description="Payment status")
|
89
|
-
expired_at: Optional[str] = Field(None, description="Expiration timestamp")
|
90
|
-
|
91
|
-
|
92
|
-
class CryptomusWebhook(BaseModel):
|
93
|
-
"""Cryptomus webhook data according to official documentation."""
|
94
|
-
model_config = ConfigDict(validate_assignment=True, extra="allow") # Allow extra fields
|
95
|
-
|
96
|
-
# Required fields from documentation
|
97
|
-
type: str = Field(..., description="Webhook type (payment/payout)")
|
98
|
-
uuid: str = Field(..., description="Payment/payout UUID")
|
99
|
-
order_id: str = Field(..., description="Order ID from your system")
|
100
|
-
amount: str = Field(..., description="Amount")
|
101
|
-
payment_amount: Optional[str] = Field(None, description="Payment amount")
|
102
|
-
payment_amount_usd: Optional[str] = Field(None, description="Payment amount in USD")
|
103
|
-
merchant_amount: Optional[str] = Field(None, description="Merchant amount after fees")
|
104
|
-
commission: Optional[str] = Field(None, description="Commission amount")
|
105
|
-
is_final: bool = Field(..., description="Is payment final")
|
106
|
-
status: str = Field(..., description="Payment status")
|
107
|
-
from_: Optional[str] = Field(None, alias="from", description="Sender address")
|
108
|
-
wallet_address_uuid: Optional[str] = Field(None, description="Wallet address UUID")
|
109
|
-
network: Optional[str] = Field(None, description="Blockchain network")
|
110
|
-
currency: str = Field(..., description="Currency")
|
111
|
-
payer_currency: Optional[str] = Field(None, description="Payer currency")
|
112
|
-
additional_data: Optional[str] = Field(None, description="Additional data")
|
113
|
-
txid: Optional[str] = Field(None, description="Transaction hash")
|
114
|
-
sign: str = Field(..., description="Webhook signature")
|
115
|
-
|
116
|
-
@property
|
117
|
-
def from_address(self) -> Optional[str]:
|
118
|
-
"""Get sender address from 'from' field."""
|
119
|
-
return self.from_
|
120
|
-
|
121
|
-
@field_validator('status')
|
122
|
-
@classmethod
|
123
|
-
def validate_status(cls, v):
|
124
|
-
"""Validate status is one of expected values."""
|
125
|
-
valid_statuses = ['check', 'paid', 'paid_over', 'fail', 'wrong_amount', 'cancel',
|
126
|
-
'system_fail', 'refund_process', 'refund_fail', 'refund_paid']
|
127
|
-
if v not in valid_statuses:
|
128
|
-
# Don't import logger here to avoid issues, just pass
|
129
|
-
pass
|
130
|
-
return v
|
131
|
-
|
132
|
-
|
133
|
-
class CryptomusStatusResponse(BaseModel):
|
134
|
-
"""Cryptomus payment status response."""
|
135
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
136
|
-
|
137
|
-
state: int = Field(..., description="Response state")
|
138
|
-
result: Optional[CryptomusPaymentInfo] = Field(None, description="Payment info")
|
139
|
-
message: Optional[str] = Field(None, description="Response message")
|
140
|
-
|
141
|
-
|
142
|
-
class CryptomusCurrenciesResponse(BaseModel):
|
143
|
-
"""Cryptomus supported currencies response."""
|
144
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
145
|
-
|
146
|
-
state: int = Field(..., description="Response state")
|
147
|
-
result: List[CryptomusCurrency] = Field(default_factory=list, description="List of currencies")
|
148
|
-
message: Optional[str] = Field(None, description="Response message")
|
149
|
-
|
150
|
-
|
151
|
-
class CryptomusNetworksResponse(BaseModel):
|
152
|
-
"""Cryptomus supported networks response."""
|
153
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
154
|
-
|
155
|
-
state: int = Field(..., description="Response state")
|
156
|
-
result: Dict[str, List[CryptomusNetwork]] = Field(default_factory=dict, description="Networks by currency")
|
157
|
-
message: Optional[str] = Field(None, description="Response message")
|
158
|
-
|
159
|
-
|
160
|
-
# =============================================================================
|
161
|
-
# MONITORING & HEALTH CHECK MODELS
|
162
|
-
# =============================================================================
|
163
|
-
|
164
|
-
class CryptomusErrorResponse(BaseModel):
|
165
|
-
"""Cryptomus API error response schema for health checks."""
|
166
|
-
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
167
|
-
|
168
|
-
error: str = Field(..., description="Error message")
|
169
|
-
|
170
|
-
@field_validator('error')
|
171
|
-
@classmethod
|
172
|
-
def validate_not_found_error(cls, v):
|
173
|
-
"""Validate this is a not found error (meaning API is responding)."""
|
174
|
-
if v.lower() not in ['not found', 'unauthorized', 'forbidden']:
|
175
|
-
raise ValueError(f"Unexpected error: {v}")
|
176
|
-
return v
|