django-cfg 1.3.5__py3-none-any.whl → 1.3.9__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/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/urls.py +1 -2
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +3 -0
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,74 +1,97 @@
|
|
1
1
|
"""
|
2
|
-
NowPayments provider for
|
2
|
+
NowPayments provider implementation for Universal Payment System v2.0.
|
3
3
|
|
4
|
-
|
4
|
+
Enhanced crypto payment provider with currency synchronization.
|
5
5
|
"""
|
6
6
|
|
7
|
-
|
7
|
+
import requests
|
8
|
+
import hashlib
|
9
|
+
import hmac
|
10
|
+
from typing import Optional, List, Dict, Any
|
8
11
|
from decimal import Decimal
|
9
12
|
from datetime import datetime
|
10
|
-
from pydantic import BaseModel, Field
|
11
|
-
import hmac
|
12
|
-
import hashlib
|
13
|
-
import json
|
14
|
-
|
15
|
-
from .base import BaseProvider, ProviderConfig, PaymentRequest
|
16
|
-
from ..types import ProviderResponse, ServiceOperationResult, NowPaymentsWebhook
|
17
|
-
from django_cfg.modules.django_currency import convert_currency
|
18
13
|
|
14
|
+
from django_cfg.modules.django_logger import get_logger
|
15
|
+
from ..base import BaseProvider
|
16
|
+
from ..models import PaymentRequest
|
17
|
+
from ...types import ProviderResponse, ServiceOperationResult
|
18
|
+
from .models import (
|
19
|
+
NowPaymentsProviderConfig,
|
20
|
+
NowPaymentsCurrency,
|
21
|
+
NowPaymentsFullCurrenciesResponse
|
22
|
+
)
|
23
|
+
from ..models import (
|
24
|
+
UniversalCurrency,
|
25
|
+
UniversalCurrenciesResponse,
|
26
|
+
CurrencySyncResult
|
27
|
+
)
|
28
|
+
from .sync import NowPaymentsCurrencySync
|
29
|
+
from .parsers import NowPaymentsCurrencyParser
|
30
|
+
from .config import NowPaymentsConfig as Config
|
19
31
|
|
20
|
-
|
21
|
-
"""
|
22
|
-
NowPayments-specific configuration.
|
23
|
-
|
24
|
-
Extends base config with NowPayments-specific fields.
|
25
|
-
"""
|
26
|
-
|
27
|
-
ipn_secret: Optional[str] = Field(None, description="IPN callback secret")
|
28
|
-
|
29
|
-
def __init__(self, **data):
|
30
|
-
"""Initialize with NowPayments defaults."""
|
31
|
-
# Set NowPayments-specific defaults
|
32
|
-
if 'provider_name' not in data:
|
33
|
-
data['provider_name'] = 'nowpayments'
|
34
|
-
|
35
|
-
if 'api_url' not in data:
|
36
|
-
sandbox = data.get('sandbox', False)
|
37
|
-
data['api_url'] = (
|
38
|
-
'https://api-sandbox.nowpayments.io/v1' if sandbox
|
39
|
-
else 'https://api.nowpayments.io/v1'
|
40
|
-
)
|
41
|
-
|
42
|
-
if 'supported_currencies' not in data:
|
43
|
-
data['supported_currencies'] = [
|
44
|
-
'BTC', 'ETH', 'LTC', 'XMR', 'USDT', 'USDC', 'ADA', 'DOT'
|
45
|
-
]
|
46
|
-
|
47
|
-
super().__init__(**data)
|
32
|
+
logger = get_logger("nowpayments")
|
48
33
|
|
49
34
|
|
50
35
|
class NowPaymentsProvider(BaseProvider):
|
51
|
-
"""
|
52
|
-
NowPayments provider implementation.
|
36
|
+
"""NowPayments cryptocurrency payment provider."""
|
53
37
|
|
54
|
-
|
55
|
-
|
38
|
+
# Map NowPayments status to universal status
|
39
|
+
STATUS_MAPPING = {
|
40
|
+
'waiting': 'pending',
|
41
|
+
'confirming': 'processing',
|
42
|
+
'confirmed': 'completed',
|
43
|
+
'sending': 'processing',
|
44
|
+
'partially_paid': 'pending',
|
45
|
+
'finished': 'completed',
|
46
|
+
'failed': 'failed',
|
47
|
+
'refunded': 'refunded',
|
48
|
+
'expired': 'expired'
|
49
|
+
}
|
56
50
|
|
57
|
-
def __init__(self, config:
|
51
|
+
def __init__(self, config: NowPaymentsProviderConfig):
|
58
52
|
"""Initialize NowPayments provider."""
|
59
53
|
super().__init__(config)
|
60
|
-
self.config:
|
54
|
+
self.config: NowPaymentsProviderConfig = config
|
55
|
+
self.sync_service = NowPaymentsCurrencySync(self.name)
|
56
|
+
self.parser = NowPaymentsCurrencyParser()
|
57
|
+
|
58
|
+
# Log initialization
|
59
|
+
api_key_str = str(self.config.api_key)
|
60
|
+
if hasattr(self.config.api_key, 'get_secret_value'):
|
61
|
+
api_key_str = self.config.api_key.get_secret_value()
|
62
|
+
|
63
|
+
logger.info(
|
64
|
+
f"🔑 NowPayments initialized: api_key={api_key_str[:10]}..., "
|
65
|
+
f"sandbox={self.is_sandbox}, base_url={self.config.api_url}"
|
66
|
+
)
|
67
|
+
|
68
|
+
# Override BaseProvider configuration methods
|
69
|
+
def get_fee_percentage(self, currency_code: str = None, currency_type: str = None) -> Decimal:
|
70
|
+
"""Get NowPayments fee percentage."""
|
71
|
+
return Config.FEE_PERCENTAGE
|
72
|
+
|
73
|
+
def get_fixed_fee_usd(self, currency_code: str = None, currency_type: str = None) -> Decimal:
|
74
|
+
"""Get NowPayments fixed fee."""
|
75
|
+
return Config.FIXED_FEE_USD
|
76
|
+
|
77
|
+
def get_min_amount_usd(self, currency_code: str = None, currency_type: str = None, is_stable: bool = False) -> Decimal:
|
78
|
+
"""Get NowPayments minimum amount."""
|
79
|
+
return Config.get_min_amount()
|
80
|
+
|
81
|
+
def get_max_amount_usd(self, currency_code: str = None, currency_type: str = None) -> Decimal:
|
82
|
+
"""Get NowPayments maximum amount."""
|
83
|
+
return Config.MAX_AMOUNT_USD
|
84
|
+
|
85
|
+
def get_confirmation_blocks(self, network_code: str) -> int:
|
86
|
+
"""Get confirmation blocks for network."""
|
87
|
+
return Config.get_confirmation_blocks(network_code)
|
88
|
+
|
89
|
+
def get_network_name(self, network_code: str) -> str:
|
90
|
+
"""Get human-readable network name."""
|
91
|
+
return Config.get_network_name(network_code)
|
61
92
|
|
62
93
|
def create_payment(self, request: PaymentRequest) -> ProviderResponse:
|
63
|
-
"""
|
64
|
-
Create payment with NowPayments.
|
65
|
-
|
66
|
-
Args:
|
67
|
-
request: Payment creation request
|
68
|
-
|
69
|
-
Returns:
|
70
|
-
ProviderResponse: NowPayments response
|
71
|
-
"""
|
94
|
+
"""Create payment with NowPayments."""
|
72
95
|
try:
|
73
96
|
self.logger.info("Creating NowPayments payment", extra={
|
74
97
|
'amount_usd': request.amount_usd,
|
@@ -76,20 +99,6 @@ class NowPaymentsProvider(BaseProvider):
|
|
76
99
|
'order_id': request.order_id
|
77
100
|
})
|
78
101
|
|
79
|
-
# Convert USD to crypto amount
|
80
|
-
try:
|
81
|
-
crypto_amount = convert_currency(
|
82
|
-
request.amount_usd,
|
83
|
-
'USD',
|
84
|
-
request.currency_code
|
85
|
-
)
|
86
|
-
except Exception as e:
|
87
|
-
return self._create_provider_response(
|
88
|
-
success=False,
|
89
|
-
raw_response={'error': f'Currency conversion failed: {e}'},
|
90
|
-
error_message=f'Currency conversion failed: {e}'
|
91
|
-
)
|
92
|
-
|
93
102
|
# Prepare NowPayments request
|
94
103
|
payment_data = {
|
95
104
|
'price_amount': request.amount_usd,
|
@@ -109,13 +118,13 @@ class NowPaymentsProvider(BaseProvider):
|
|
109
118
|
if request.customer_email:
|
110
119
|
payment_data['customer_email'] = request.customer_email
|
111
120
|
|
112
|
-
# Add IPN callback URL
|
113
|
-
if
|
114
|
-
payment_data['ipn_callback_url'] = self.
|
121
|
+
# Add IPN callback URL if configured
|
122
|
+
if self.config.callback_url:
|
123
|
+
payment_data['ipn_callback_url'] = self.config.callback_url
|
115
124
|
|
116
125
|
# Make API request
|
117
126
|
headers = {
|
118
|
-
'x-api-key': self.
|
127
|
+
'x-api-key': self._get_api_key()
|
119
128
|
}
|
120
129
|
|
121
130
|
response_data = self._make_request(
|
@@ -126,7 +135,7 @@ class NowPaymentsProvider(BaseProvider):
|
|
126
135
|
)
|
127
136
|
|
128
137
|
# Parse NowPayments response
|
129
|
-
if 'payment_id' in response_data:
|
138
|
+
if response_data and 'payment_id' in response_data:
|
130
139
|
# Successful payment creation
|
131
140
|
payment_url = response_data.get('invoice_url') or response_data.get('pay_url')
|
132
141
|
|
@@ -135,19 +144,18 @@ class NowPaymentsProvider(BaseProvider):
|
|
135
144
|
raw_response=response_data,
|
136
145
|
provider_payment_id=response_data['payment_id'],
|
137
146
|
status='waiting', # NowPayments initial status
|
138
|
-
amount=Decimal(str(
|
147
|
+
amount=Decimal(str(response_data.get('pay_amount', 0))),
|
139
148
|
currency=request.currency_code,
|
140
149
|
payment_url=payment_url,
|
141
150
|
wallet_address=response_data.get('pay_address'),
|
142
|
-
qr_code_url=response_data.get('qr_code_url'),
|
143
151
|
expires_at=self._parse_expiry_time(response_data.get('expiration_estimate_date'))
|
144
152
|
)
|
145
153
|
else:
|
146
154
|
# Error response
|
147
|
-
error_message = response_data.get('message', 'Unknown error')
|
155
|
+
error_message = response_data.get('message', 'Unknown error') if response_data else 'No response'
|
148
156
|
return self._create_provider_response(
|
149
157
|
success=False,
|
150
|
-
raw_response=response_data,
|
158
|
+
raw_response=response_data or {},
|
151
159
|
error_message=error_message
|
152
160
|
)
|
153
161
|
|
@@ -163,22 +171,14 @@ class NowPaymentsProvider(BaseProvider):
|
|
163
171
|
)
|
164
172
|
|
165
173
|
def get_payment_status(self, provider_payment_id: str) -> ProviderResponse:
|
166
|
-
"""
|
167
|
-
Get payment status from NowPayments.
|
168
|
-
|
169
|
-
Args:
|
170
|
-
provider_payment_id: NowPayments payment ID
|
171
|
-
|
172
|
-
Returns:
|
173
|
-
ProviderResponse: Current payment status
|
174
|
-
"""
|
174
|
+
"""Get payment status from NowPayments."""
|
175
175
|
try:
|
176
176
|
self.logger.debug("Getting NowPayments payment status", extra={
|
177
177
|
'payment_id': provider_payment_id
|
178
178
|
})
|
179
179
|
|
180
180
|
headers = {
|
181
|
-
'x-api-key': self.
|
181
|
+
'x-api-key': self._get_api_key()
|
182
182
|
}
|
183
183
|
|
184
184
|
response_data = self._make_request(
|
@@ -187,21 +187,24 @@ class NowPaymentsProvider(BaseProvider):
|
|
187
187
|
headers=headers
|
188
188
|
)
|
189
189
|
|
190
|
-
if 'payment_status' in response_data:
|
190
|
+
if response_data and 'payment_status' in response_data:
|
191
|
+
provider_status = response_data['payment_status']
|
192
|
+
universal_status = self.STATUS_MAPPING.get(provider_status, 'unknown')
|
193
|
+
|
191
194
|
return self._create_provider_response(
|
192
195
|
success=True,
|
193
196
|
raw_response=response_data,
|
194
197
|
provider_payment_id=provider_payment_id,
|
195
|
-
status=
|
198
|
+
status=universal_status,
|
196
199
|
amount=Decimal(str(response_data.get('pay_amount', 0))),
|
197
200
|
currency=response_data.get('pay_currency'),
|
198
201
|
wallet_address=response_data.get('pay_address')
|
199
202
|
)
|
200
203
|
else:
|
201
|
-
error_message = response_data.get('message', 'Payment not found')
|
204
|
+
error_message = response_data.get('message', 'Payment not found') if response_data else 'No response'
|
202
205
|
return self._create_provider_response(
|
203
206
|
success=False,
|
204
|
-
raw_response=response_data,
|
207
|
+
raw_response=response_data or {},
|
205
208
|
error_message=error_message
|
206
209
|
)
|
207
210
|
|
@@ -217,26 +220,21 @@ class NowPaymentsProvider(BaseProvider):
|
|
217
220
|
)
|
218
221
|
|
219
222
|
def get_supported_currencies(self) -> ServiceOperationResult:
|
220
|
-
"""
|
221
|
-
Get supported currencies from NowPayments.
|
222
|
-
|
223
|
-
Returns:
|
224
|
-
ServiceOperationResult: List of supported currencies
|
225
|
-
"""
|
223
|
+
"""Get supported currencies from NowPayments."""
|
226
224
|
try:
|
227
225
|
self.logger.debug("Getting NowPayments supported currencies")
|
228
226
|
|
229
227
|
headers = {
|
230
|
-
'x-api-key': self.
|
228
|
+
'x-api-key': self._get_api_key()
|
231
229
|
}
|
232
230
|
|
233
231
|
response_data = self._make_request(
|
234
232
|
method='GET',
|
235
|
-
endpoint='currencies',
|
233
|
+
endpoint='full-currencies',
|
236
234
|
headers=headers
|
237
235
|
)
|
238
236
|
|
239
|
-
if 'currencies' in response_data:
|
237
|
+
if response_data and 'currencies' in response_data:
|
240
238
|
currencies = response_data['currencies']
|
241
239
|
|
242
240
|
return ServiceOperationResult(
|
@@ -264,17 +262,113 @@ class NowPaymentsProvider(BaseProvider):
|
|
264
262
|
error_code="currencies_fetch_error"
|
265
263
|
)
|
266
264
|
|
267
|
-
def
|
268
|
-
"""
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
265
|
+
def get_parsed_currencies(self) -> UniversalCurrenciesResponse:
|
266
|
+
"""Get parsed and normalized currencies from NowPayments."""
|
267
|
+
try:
|
268
|
+
# Use full-currencies endpoint to get detailed currency info
|
269
|
+
headers = {
|
270
|
+
'x-api-key': self._get_api_key()
|
271
|
+
}
|
272
|
+
|
273
|
+
response_data = self._make_request(
|
274
|
+
method='GET',
|
275
|
+
endpoint='full-currencies',
|
276
|
+
headers=headers
|
277
|
+
)
|
278
|
+
|
279
|
+
if not response_data or 'currencies' not in response_data:
|
280
|
+
return UniversalCurrenciesResponse(currencies=[])
|
281
|
+
|
282
|
+
universal_currencies = []
|
274
283
|
|
275
|
-
|
276
|
-
|
277
|
-
|
284
|
+
for currency_data in response_data['currencies']:
|
285
|
+
if not currency_data.get('enable', True):
|
286
|
+
continue # Skip disabled currencies
|
287
|
+
|
288
|
+
provider_code = currency_data.get('code', '').upper()
|
289
|
+
if not provider_code:
|
290
|
+
continue
|
291
|
+
|
292
|
+
# Parse provider code into base currency + network using API data
|
293
|
+
currency_name = currency_data.get('name', '')
|
294
|
+
api_network = currency_data.get('network')
|
295
|
+
ticker = currency_data.get('ticker', '')
|
296
|
+
|
297
|
+
# Use parser to extract base currency and network
|
298
|
+
parse_result = self.parser.parse_currency_code(
|
299
|
+
provider_code, currency_name, api_network, ticker
|
300
|
+
)
|
301
|
+
|
302
|
+
# Skip currencies that should be filtered out (empty network duplicates)
|
303
|
+
if parse_result[0] is None:
|
304
|
+
continue
|
305
|
+
|
306
|
+
base_currency_code, network_code = parse_result
|
307
|
+
|
308
|
+
# Determine currency type
|
309
|
+
currency_type = 'fiat' if network_code is None else 'crypto'
|
310
|
+
|
311
|
+
# Generate proper currency name
|
312
|
+
proper_name = self.parser.generate_currency_name(
|
313
|
+
base_currency_code, network_code, currency_name
|
314
|
+
)
|
315
|
+
|
316
|
+
universal_currency = UniversalCurrency(
|
317
|
+
provider_currency_code=provider_code,
|
318
|
+
base_currency_code=base_currency_code,
|
319
|
+
network_code=network_code,
|
320
|
+
name=proper_name, # Use generated name instead of API name
|
321
|
+
currency_type=currency_type,
|
322
|
+
is_enabled=currency_data.get('enable', True),
|
323
|
+
is_popular=currency_data.get('is_popular', False),
|
324
|
+
is_stable=currency_data.get('is_stable', False),
|
325
|
+
priority=currency_data.get('priority', 0),
|
326
|
+
logo_url=currency_data.get('logo_url', ''),
|
327
|
+
available_for_payment=currency_data.get('available_for_payment', True),
|
328
|
+
available_for_payout=currency_data.get('available_for_payout', True),
|
329
|
+
raw_data=currency_data
|
330
|
+
)
|
331
|
+
|
332
|
+
universal_currencies.append(universal_currency)
|
333
|
+
|
334
|
+
return UniversalCurrenciesResponse(currencies=universal_currencies)
|
335
|
+
|
336
|
+
except Exception as e:
|
337
|
+
logger.error(f"Error parsing currencies: {e}")
|
338
|
+
return UniversalCurrenciesResponse(currencies=[])
|
339
|
+
|
340
|
+
def sync_currencies_to_db(self) -> CurrencySyncResult:
|
341
|
+
"""Sync currencies from NowPayments API to database."""
|
342
|
+
try:
|
343
|
+
self.logger.info("Starting NowPayments currency synchronization")
|
344
|
+
|
345
|
+
# Get parsed currencies from API
|
346
|
+
currencies_response = self.get_parsed_currencies()
|
347
|
+
|
348
|
+
if not currencies_response.currencies:
|
349
|
+
return CurrencySyncResult(
|
350
|
+
errors=["No currencies received from NowPayments API"]
|
351
|
+
)
|
352
|
+
|
353
|
+
# Sync to database
|
354
|
+
result = self.sync_service.sync_currencies_to_db(currencies_response.currencies)
|
355
|
+
|
356
|
+
self.logger.info(
|
357
|
+
f"NowPayments currency sync completed: "
|
358
|
+
f"{result.currencies_created} currencies created, "
|
359
|
+
f"{result.provider_currencies_created} provider currencies created, "
|
360
|
+
f"{len(result.errors)} errors"
|
361
|
+
)
|
362
|
+
|
363
|
+
return result
|
364
|
+
|
365
|
+
except Exception as e:
|
366
|
+
error_msg = f"Currency sync failed: {e}"
|
367
|
+
self.logger.error(error_msg)
|
368
|
+
return CurrencySyncResult(errors=[error_msg])
|
369
|
+
|
370
|
+
def validate_webhook(self, payload: Dict[str, Any], signature: str = None) -> ServiceOperationResult:
|
371
|
+
"""Validate NowPayments IPN webhook."""
|
278
372
|
try:
|
279
373
|
self.logger.debug("Validating NowPayments webhook", extra={
|
280
374
|
'has_signature': bool(signature),
|
@@ -283,6 +377,7 @@ class NowPaymentsProvider(BaseProvider):
|
|
283
377
|
|
284
378
|
# Validate payload structure
|
285
379
|
try:
|
380
|
+
from .models import NowPaymentsWebhook
|
286
381
|
webhook_data = NowPaymentsWebhook(**payload)
|
287
382
|
except Exception as e:
|
288
383
|
return ServiceOperationResult(
|
@@ -322,74 +417,62 @@ class NowPaymentsProvider(BaseProvider):
|
|
322
417
|
error_code="validation_error"
|
323
418
|
)
|
324
419
|
|
325
|
-
def
|
326
|
-
"""
|
327
|
-
Get exchange rate from NowPayments.
|
328
|
-
|
329
|
-
Args:
|
330
|
-
from_currency: Source currency
|
331
|
-
to_currency: Target currency
|
332
|
-
|
333
|
-
Returns:
|
334
|
-
ServiceOperationResult: Exchange rate
|
335
|
-
"""
|
420
|
+
def health_check(self) -> ServiceOperationResult:
|
421
|
+
"""Perform NowPayments-specific health check."""
|
336
422
|
try:
|
337
|
-
|
338
|
-
'from': from_currency,
|
339
|
-
'to': to_currency
|
340
|
-
})
|
341
|
-
|
423
|
+
# Test API connectivity by getting status
|
342
424
|
headers = {
|
343
|
-
'x-api-key': self.
|
425
|
+
'x-api-key': self._get_api_key()
|
344
426
|
}
|
345
427
|
|
346
428
|
response_data = self._make_request(
|
347
429
|
method='GET',
|
348
|
-
endpoint=
|
430
|
+
endpoint='status',
|
349
431
|
headers=headers
|
350
432
|
)
|
351
433
|
|
352
|
-
if '
|
353
|
-
|
434
|
+
if response_data and response_data.get('message') == 'OK':
|
435
|
+
# Also check currencies endpoint
|
436
|
+
currencies_result = self.get_supported_currencies()
|
437
|
+
currency_count = len(currencies_result.data.get('currencies', [])) if currencies_result.success else 0
|
354
438
|
|
355
439
|
return ServiceOperationResult(
|
356
440
|
success=True,
|
357
|
-
message="
|
441
|
+
message="NowPayments provider is healthy",
|
358
442
|
data={
|
359
|
-
'
|
360
|
-
'
|
361
|
-
'
|
362
|
-
'
|
443
|
+
'provider': self.name,
|
444
|
+
'sandbox': self.is_sandbox,
|
445
|
+
'api_url': self.config.api_url,
|
446
|
+
'supported_currencies': currency_count,
|
447
|
+
'has_ipn_secret': bool(self.config.ipn_secret),
|
448
|
+
'api_key_configured': bool(self.config.api_key)
|
363
449
|
}
|
364
450
|
)
|
365
451
|
else:
|
366
452
|
return ServiceOperationResult(
|
367
453
|
success=False,
|
368
|
-
message="
|
369
|
-
error_code="
|
454
|
+
message="NowPayments API connectivity failed",
|
455
|
+
error_code="api_connectivity_failed",
|
456
|
+
data={
|
457
|
+
'provider': self.name,
|
458
|
+
'response': response_data
|
459
|
+
}
|
370
460
|
)
|
371
461
|
|
372
462
|
except Exception as e:
|
373
|
-
self.logger.error(f"NowPayments exchange rate failed: {e}")
|
374
|
-
|
375
463
|
return ServiceOperationResult(
|
376
464
|
success=False,
|
377
|
-
message=f"
|
378
|
-
error_code="
|
465
|
+
message=f"NowPayments health check error: {e}",
|
466
|
+
error_code="health_check_error",
|
467
|
+
data={'provider': self.name}
|
379
468
|
)
|
380
469
|
|
470
|
+
|
381
471
|
def _validate_ipn_signature(self, payload: Dict[str, Any], signature: str) -> bool:
|
382
|
-
"""
|
383
|
-
Validate IPN signature using HMAC-SHA512.
|
384
|
-
|
385
|
-
Args:
|
386
|
-
payload: Webhook payload
|
387
|
-
signature: Received signature
|
388
|
-
|
389
|
-
Returns:
|
390
|
-
bool: True if signature is valid
|
391
|
-
"""
|
472
|
+
"""Validate IPN signature using HMAC-SHA512."""
|
392
473
|
try:
|
474
|
+
import json
|
475
|
+
|
393
476
|
# Sort payload and create canonical string
|
394
477
|
sorted_payload = json.dumps(payload, separators=(',', ':'), sort_keys=True)
|
395
478
|
|
@@ -408,15 +491,7 @@ class NowPaymentsProvider(BaseProvider):
|
|
408
491
|
return False
|
409
492
|
|
410
493
|
def _parse_expiry_time(self, expiry_str: Optional[str]) -> Optional[datetime]:
|
411
|
-
"""
|
412
|
-
Parse NowPayments expiry time string.
|
413
|
-
|
414
|
-
Args:
|
415
|
-
expiry_str: Expiry time string from NowPayments
|
416
|
-
|
417
|
-
Returns:
|
418
|
-
Optional[datetime]: Parsed expiry time
|
419
|
-
"""
|
494
|
+
"""Parse NowPayments expiry time string."""
|
420
495
|
if not expiry_str:
|
421
496
|
return None
|
422
497
|
|
@@ -427,52 +502,8 @@ class NowPaymentsProvider(BaseProvider):
|
|
427
502
|
self.logger.warning(f"Failed to parse expiry time: {expiry_str}")
|
428
503
|
return None
|
429
504
|
|
430
|
-
def
|
431
|
-
"""
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
callback_url: IPN callback URL
|
436
|
-
"""
|
437
|
-
self._ipn_callback_url = callback_url
|
438
|
-
self.logger.info(f"Set IPN callback URL: {callback_url}")
|
439
|
-
|
440
|
-
def health_check(self) -> ServiceOperationResult:
|
441
|
-
"""Perform NowPayments-specific health check."""
|
442
|
-
try:
|
443
|
-
# Test API connectivity by getting currencies
|
444
|
-
currencies_result = self.get_supported_currencies()
|
445
|
-
|
446
|
-
if currencies_result.success:
|
447
|
-
currency_count = len(currencies_result.data.get('currencies', []))
|
448
|
-
|
449
|
-
return ServiceOperationResult(
|
450
|
-
success=True,
|
451
|
-
message="NowPayments provider is healthy",
|
452
|
-
data={
|
453
|
-
'provider': self.name,
|
454
|
-
'sandbox': self.is_sandbox,
|
455
|
-
'api_url': self.config.api_url,
|
456
|
-
'supported_currencies': currency_count,
|
457
|
-
'has_ipn_secret': bool(self.config.ipn_secret),
|
458
|
-
'api_key_configured': bool(self.config.api_key)
|
459
|
-
}
|
460
|
-
)
|
461
|
-
else:
|
462
|
-
return ServiceOperationResult(
|
463
|
-
success=False,
|
464
|
-
message="NowPayments API connectivity failed",
|
465
|
-
error_code="api_connectivity_failed",
|
466
|
-
data={
|
467
|
-
'provider': self.name,
|
468
|
-
'error': currencies_result.message
|
469
|
-
}
|
470
|
-
)
|
471
|
-
|
472
|
-
except Exception as e:
|
473
|
-
return ServiceOperationResult(
|
474
|
-
success=False,
|
475
|
-
message=f"NowPayments health check error: {e}",
|
476
|
-
error_code="health_check_error",
|
477
|
-
data={'provider': self.name}
|
478
|
-
)
|
505
|
+
def _get_api_key(self) -> str:
|
506
|
+
"""Get API key as string."""
|
507
|
+
if hasattr(self.config.api_key, 'get_secret_value'):
|
508
|
+
return self.config.api_key.get_secret_value()
|
509
|
+
return str(self.config.api_key)
|