django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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/cleanup_expired_data.py +419 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
- 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/management/commands/process_pending_payments.py +357 -0
- django_cfg/apps/payments/management/commands/test_providers.py +434 -0
- 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 +153 -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_service/__init__.py +143 -0
- django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
- django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
- django_cfg/apps/payments/services/cache_service/keys.py +49 -0
- django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
- django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
- 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 +371 -465
- 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 +227 -570
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -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/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/simple_cache.py +0 -135
- 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.3.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,478 @@
|
|
1
|
+
"""
|
2
|
+
NowPayments provider for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Implementation of NowPayments API integration with unified interface.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, Optional, List
|
8
|
+
from decimal import Decimal
|
9
|
+
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
|
+
|
19
|
+
|
20
|
+
class NowPaymentsConfig(ProviderConfig):
|
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)
|
48
|
+
|
49
|
+
|
50
|
+
class NowPaymentsProvider(BaseProvider):
|
51
|
+
"""
|
52
|
+
NowPayments provider implementation.
|
53
|
+
|
54
|
+
Handles payment creation, status checking, and webhook validation for NowPayments.
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(self, config: NowPaymentsConfig):
|
58
|
+
"""Initialize NowPayments provider."""
|
59
|
+
super().__init__(config)
|
60
|
+
self.config: NowPaymentsConfig = config
|
61
|
+
|
62
|
+
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
|
+
"""
|
72
|
+
try:
|
73
|
+
self.logger.info("Creating NowPayments payment", extra={
|
74
|
+
'amount_usd': request.amount_usd,
|
75
|
+
'currency': request.currency_code,
|
76
|
+
'order_id': request.order_id
|
77
|
+
})
|
78
|
+
|
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
|
+
# Prepare NowPayments request
|
94
|
+
payment_data = {
|
95
|
+
'price_amount': request.amount_usd,
|
96
|
+
'price_currency': 'USD',
|
97
|
+
'pay_currency': request.currency_code,
|
98
|
+
'order_id': request.order_id,
|
99
|
+
'order_description': request.description or f'Payment {request.order_id}',
|
100
|
+
}
|
101
|
+
|
102
|
+
# Add optional fields
|
103
|
+
if request.callback_url:
|
104
|
+
payment_data['success_url'] = request.callback_url
|
105
|
+
|
106
|
+
if request.cancel_url:
|
107
|
+
payment_data['cancel_url'] = request.cancel_url
|
108
|
+
|
109
|
+
if request.customer_email:
|
110
|
+
payment_data['customer_email'] = request.customer_email
|
111
|
+
|
112
|
+
# Add IPN callback URL (would be configured via webhook service)
|
113
|
+
if hasattr(self, '_ipn_callback_url'):
|
114
|
+
payment_data['ipn_callback_url'] = self._ipn_callback_url
|
115
|
+
|
116
|
+
# Make API request
|
117
|
+
headers = {
|
118
|
+
'x-api-key': self.config.api_key
|
119
|
+
}
|
120
|
+
|
121
|
+
response_data = self._make_request(
|
122
|
+
method='POST',
|
123
|
+
endpoint='payment',
|
124
|
+
data=payment_data,
|
125
|
+
headers=headers
|
126
|
+
)
|
127
|
+
|
128
|
+
# Parse NowPayments response
|
129
|
+
if 'payment_id' in response_data:
|
130
|
+
# Successful payment creation
|
131
|
+
payment_url = response_data.get('invoice_url') or response_data.get('pay_url')
|
132
|
+
|
133
|
+
return self._create_provider_response(
|
134
|
+
success=True,
|
135
|
+
raw_response=response_data,
|
136
|
+
provider_payment_id=response_data['payment_id'],
|
137
|
+
status='waiting', # NowPayments initial status
|
138
|
+
amount=Decimal(str(crypto_amount)),
|
139
|
+
currency=request.currency_code,
|
140
|
+
payment_url=payment_url,
|
141
|
+
wallet_address=response_data.get('pay_address'),
|
142
|
+
qr_code_url=response_data.get('qr_code_url'),
|
143
|
+
expires_at=self._parse_expiry_time(response_data.get('expiration_estimate_date'))
|
144
|
+
)
|
145
|
+
else:
|
146
|
+
# Error response
|
147
|
+
error_message = response_data.get('message', 'Unknown error')
|
148
|
+
return self._create_provider_response(
|
149
|
+
success=False,
|
150
|
+
raw_response=response_data,
|
151
|
+
error_message=error_message
|
152
|
+
)
|
153
|
+
|
154
|
+
except Exception as e:
|
155
|
+
self.logger.error(f"NowPayments payment creation failed: {e}", extra={
|
156
|
+
'order_id': request.order_id
|
157
|
+
})
|
158
|
+
|
159
|
+
return self._create_provider_response(
|
160
|
+
success=False,
|
161
|
+
raw_response={'error': str(e)},
|
162
|
+
error_message=f'Payment creation failed: {e}'
|
163
|
+
)
|
164
|
+
|
165
|
+
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
|
+
"""
|
175
|
+
try:
|
176
|
+
self.logger.debug("Getting NowPayments payment status", extra={
|
177
|
+
'payment_id': provider_payment_id
|
178
|
+
})
|
179
|
+
|
180
|
+
headers = {
|
181
|
+
'x-api-key': self.config.api_key
|
182
|
+
}
|
183
|
+
|
184
|
+
response_data = self._make_request(
|
185
|
+
method='GET',
|
186
|
+
endpoint=f'payment/{provider_payment_id}',
|
187
|
+
headers=headers
|
188
|
+
)
|
189
|
+
|
190
|
+
if 'payment_status' in response_data:
|
191
|
+
return self._create_provider_response(
|
192
|
+
success=True,
|
193
|
+
raw_response=response_data,
|
194
|
+
provider_payment_id=provider_payment_id,
|
195
|
+
status=response_data['payment_status'],
|
196
|
+
amount=Decimal(str(response_data.get('pay_amount', 0))),
|
197
|
+
currency=response_data.get('pay_currency'),
|
198
|
+
wallet_address=response_data.get('pay_address')
|
199
|
+
)
|
200
|
+
else:
|
201
|
+
error_message = response_data.get('message', 'Payment not found')
|
202
|
+
return self._create_provider_response(
|
203
|
+
success=False,
|
204
|
+
raw_response=response_data,
|
205
|
+
error_message=error_message
|
206
|
+
)
|
207
|
+
|
208
|
+
except Exception as e:
|
209
|
+
self.logger.error(f"NowPayments status check failed: {e}", extra={
|
210
|
+
'payment_id': provider_payment_id
|
211
|
+
})
|
212
|
+
|
213
|
+
return self._create_provider_response(
|
214
|
+
success=False,
|
215
|
+
raw_response={'error': str(e)},
|
216
|
+
error_message=f'Status check failed: {e}'
|
217
|
+
)
|
218
|
+
|
219
|
+
def get_supported_currencies(self) -> ServiceOperationResult:
|
220
|
+
"""
|
221
|
+
Get supported currencies from NowPayments.
|
222
|
+
|
223
|
+
Returns:
|
224
|
+
ServiceOperationResult: List of supported currencies
|
225
|
+
"""
|
226
|
+
try:
|
227
|
+
self.logger.debug("Getting NowPayments supported currencies")
|
228
|
+
|
229
|
+
headers = {
|
230
|
+
'x-api-key': self.config.api_key
|
231
|
+
}
|
232
|
+
|
233
|
+
response_data = self._make_request(
|
234
|
+
method='GET',
|
235
|
+
endpoint='currencies',
|
236
|
+
headers=headers
|
237
|
+
)
|
238
|
+
|
239
|
+
if 'currencies' in response_data:
|
240
|
+
currencies = response_data['currencies']
|
241
|
+
|
242
|
+
return ServiceOperationResult(
|
243
|
+
success=True,
|
244
|
+
message=f"Retrieved {len(currencies)} supported currencies",
|
245
|
+
data={
|
246
|
+
'currencies': currencies,
|
247
|
+
'count': len(currencies),
|
248
|
+
'provider': self.name
|
249
|
+
}
|
250
|
+
)
|
251
|
+
else:
|
252
|
+
return ServiceOperationResult(
|
253
|
+
success=False,
|
254
|
+
message="Failed to get currencies from NowPayments",
|
255
|
+
error_code="currencies_fetch_failed"
|
256
|
+
)
|
257
|
+
|
258
|
+
except Exception as e:
|
259
|
+
self.logger.error(f"NowPayments currencies fetch failed: {e}")
|
260
|
+
|
261
|
+
return ServiceOperationResult(
|
262
|
+
success=False,
|
263
|
+
message=f"Currencies fetch error: {e}",
|
264
|
+
error_code="currencies_fetch_error"
|
265
|
+
)
|
266
|
+
|
267
|
+
def validate_webhook(self, payload: Dict[str, Any], signature: str = None) -> ServiceOperationResult:
|
268
|
+
"""
|
269
|
+
Validate NowPayments IPN webhook.
|
270
|
+
|
271
|
+
Args:
|
272
|
+
payload: Webhook payload
|
273
|
+
signature: HMAC signature (optional)
|
274
|
+
|
275
|
+
Returns:
|
276
|
+
ServiceOperationResult: Validation result
|
277
|
+
"""
|
278
|
+
try:
|
279
|
+
self.logger.debug("Validating NowPayments webhook", extra={
|
280
|
+
'has_signature': bool(signature),
|
281
|
+
'payment_id': payload.get('payment_id')
|
282
|
+
})
|
283
|
+
|
284
|
+
# Validate payload structure
|
285
|
+
try:
|
286
|
+
webhook_data = NowPaymentsWebhook(**payload)
|
287
|
+
except Exception as e:
|
288
|
+
return ServiceOperationResult(
|
289
|
+
success=False,
|
290
|
+
message=f"Invalid webhook payload: {e}",
|
291
|
+
error_code="invalid_payload"
|
292
|
+
)
|
293
|
+
|
294
|
+
# Validate signature if provided and secret is configured
|
295
|
+
if signature and self.config.ipn_secret:
|
296
|
+
is_valid_signature = self._validate_ipn_signature(payload, signature)
|
297
|
+
if not is_valid_signature:
|
298
|
+
return ServiceOperationResult(
|
299
|
+
success=False,
|
300
|
+
message="Invalid webhook signature",
|
301
|
+
error_code="invalid_signature"
|
302
|
+
)
|
303
|
+
|
304
|
+
return ServiceOperationResult(
|
305
|
+
success=True,
|
306
|
+
message="Webhook validated successfully",
|
307
|
+
data={
|
308
|
+
'provider': self.name,
|
309
|
+
'payment_id': webhook_data.payment_id,
|
310
|
+
'status': webhook_data.payment_status,
|
311
|
+
'signature_validated': bool(signature and self.config.ipn_secret),
|
312
|
+
'webhook_data': webhook_data.model_dump()
|
313
|
+
}
|
314
|
+
)
|
315
|
+
|
316
|
+
except Exception as e:
|
317
|
+
self.logger.error(f"NowPayments webhook validation failed: {e}")
|
318
|
+
|
319
|
+
return ServiceOperationResult(
|
320
|
+
success=False,
|
321
|
+
message=f"Webhook validation error: {e}",
|
322
|
+
error_code="validation_error"
|
323
|
+
)
|
324
|
+
|
325
|
+
def get_exchange_rate(self, from_currency: str, to_currency: str) -> ServiceOperationResult:
|
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
|
+
"""
|
336
|
+
try:
|
337
|
+
self.logger.debug("Getting NowPayments exchange rate", extra={
|
338
|
+
'from': from_currency,
|
339
|
+
'to': to_currency
|
340
|
+
})
|
341
|
+
|
342
|
+
headers = {
|
343
|
+
'x-api-key': self.config.api_key
|
344
|
+
}
|
345
|
+
|
346
|
+
response_data = self._make_request(
|
347
|
+
method='GET',
|
348
|
+
endpoint=f'exchange-amount/{from_currency}-{to_currency}',
|
349
|
+
headers=headers
|
350
|
+
)
|
351
|
+
|
352
|
+
if 'estimated_amount' in response_data:
|
353
|
+
rate = float(response_data['estimated_amount'])
|
354
|
+
|
355
|
+
return ServiceOperationResult(
|
356
|
+
success=True,
|
357
|
+
message="Exchange rate retrieved",
|
358
|
+
data={
|
359
|
+
'from_currency': from_currency,
|
360
|
+
'to_currency': to_currency,
|
361
|
+
'rate': rate,
|
362
|
+
'provider': self.name
|
363
|
+
}
|
364
|
+
)
|
365
|
+
else:
|
366
|
+
return ServiceOperationResult(
|
367
|
+
success=False,
|
368
|
+
message="Exchange rate not available",
|
369
|
+
error_code="rate_not_available"
|
370
|
+
)
|
371
|
+
|
372
|
+
except Exception as e:
|
373
|
+
self.logger.error(f"NowPayments exchange rate failed: {e}")
|
374
|
+
|
375
|
+
return ServiceOperationResult(
|
376
|
+
success=False,
|
377
|
+
message=f"Exchange rate error: {e}",
|
378
|
+
error_code="rate_fetch_error"
|
379
|
+
)
|
380
|
+
|
381
|
+
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
|
+
"""
|
392
|
+
try:
|
393
|
+
# Sort payload and create canonical string
|
394
|
+
sorted_payload = json.dumps(payload, separators=(',', ':'), sort_keys=True)
|
395
|
+
|
396
|
+
# Calculate expected signature
|
397
|
+
expected_signature = hmac.new(
|
398
|
+
self.config.ipn_secret.encode('utf-8'),
|
399
|
+
sorted_payload.encode('utf-8'),
|
400
|
+
hashlib.sha512
|
401
|
+
).hexdigest()
|
402
|
+
|
403
|
+
# Compare signatures
|
404
|
+
return hmac.compare_digest(expected_signature, signature)
|
405
|
+
|
406
|
+
except Exception as e:
|
407
|
+
self.logger.error(f"Signature validation error: {e}")
|
408
|
+
return False
|
409
|
+
|
410
|
+
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
|
+
"""
|
420
|
+
if not expiry_str:
|
421
|
+
return None
|
422
|
+
|
423
|
+
try:
|
424
|
+
# NowPayments typically returns ISO format
|
425
|
+
return datetime.fromisoformat(expiry_str.replace('Z', '+00:00'))
|
426
|
+
except Exception:
|
427
|
+
self.logger.warning(f"Failed to parse expiry time: {expiry_str}")
|
428
|
+
return None
|
429
|
+
|
430
|
+
def set_ipn_callback_url(self, callback_url: str):
|
431
|
+
"""
|
432
|
+
Set IPN callback URL for payments.
|
433
|
+
|
434
|
+
Args:
|
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
|
+
)
|