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
@@ -0,0 +1,410 @@
|
|
1
|
+
"""
|
2
|
+
Webhook service for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Handles webhook validation and processing from payment providers.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional, Dict, Any
|
8
|
+
import json
|
9
|
+
from django.utils import timezone
|
10
|
+
from django.db import models
|
11
|
+
|
12
|
+
from .base import BaseService
|
13
|
+
from ..types import (
|
14
|
+
WebhookValidationRequest, WebhookProcessingResult, NowPaymentsWebhook,
|
15
|
+
WebhookSignature, ServiceOperationResult
|
16
|
+
)
|
17
|
+
from ...models import UniversalPayment
|
18
|
+
from .payment_service import PaymentService
|
19
|
+
from .balance_service import BalanceService
|
20
|
+
# ConfigService removed - using direct Constance access
|
21
|
+
|
22
|
+
|
23
|
+
class WebhookService(BaseService):
|
24
|
+
"""
|
25
|
+
Webhook service with validation and processing logic.
|
26
|
+
|
27
|
+
Handles webhook operations using Pydantic validation and provider-specific logic.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self):
|
31
|
+
"""Initialize webhook service with dependencies."""
|
32
|
+
super().__init__()
|
33
|
+
self.payment_service = PaymentService()
|
34
|
+
self.balance_service = BalanceService()
|
35
|
+
# Direct Constance access instead of ConfigService
|
36
|
+
|
37
|
+
def validate_webhook(self, request: WebhookValidationRequest) -> ServiceOperationResult:
|
38
|
+
"""
|
39
|
+
Validate webhook signature and payload.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
request: Webhook validation request
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
ServiceOperationResult: Validation result
|
46
|
+
"""
|
47
|
+
try:
|
48
|
+
# Validate request
|
49
|
+
if isinstance(request, dict):
|
50
|
+
request = WebhookValidationRequest(**request)
|
51
|
+
|
52
|
+
self.logger.info("Validating webhook", extra={
|
53
|
+
'provider': request.provider,
|
54
|
+
'has_signature': bool(request.signature)
|
55
|
+
})
|
56
|
+
|
57
|
+
# Provider-specific validation
|
58
|
+
if request.provider.lower() == 'nowpayments':
|
59
|
+
return self._validate_nowpayments_webhook(request)
|
60
|
+
else:
|
61
|
+
return self._create_error_result(
|
62
|
+
f"Unsupported provider: {request.provider}",
|
63
|
+
"unsupported_provider"
|
64
|
+
)
|
65
|
+
|
66
|
+
except Exception as e:
|
67
|
+
return self._handle_exception(
|
68
|
+
"validate_webhook", e,
|
69
|
+
provider=request.provider if hasattr(request, 'provider') else None
|
70
|
+
)
|
71
|
+
|
72
|
+
def process_webhook(self, request: WebhookValidationRequest) -> WebhookProcessingResult:
|
73
|
+
"""
|
74
|
+
Process validated webhook and update payment status.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
request: Webhook validation request
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
WebhookProcessingResult: Processing result with actions taken
|
81
|
+
"""
|
82
|
+
try:
|
83
|
+
# First validate webhook
|
84
|
+
validation_result = self.validate_webhook(request)
|
85
|
+
if not validation_result.success:
|
86
|
+
return WebhookProcessingResult(
|
87
|
+
success=False,
|
88
|
+
provider=request.provider,
|
89
|
+
error_message=validation_result.message,
|
90
|
+
processed=False
|
91
|
+
)
|
92
|
+
|
93
|
+
self.logger.info("Processing webhook", extra={
|
94
|
+
'provider': request.provider,
|
95
|
+
'payload_keys': list(request.payload.keys())
|
96
|
+
})
|
97
|
+
|
98
|
+
# Provider-specific processing
|
99
|
+
if request.provider.lower() == 'nowpayments':
|
100
|
+
return self._process_nowpayments_webhook(request)
|
101
|
+
else:
|
102
|
+
return WebhookProcessingResult(
|
103
|
+
success=False,
|
104
|
+
provider=request.provider,
|
105
|
+
error_message=f"Unsupported provider: {request.provider}",
|
106
|
+
processed=False
|
107
|
+
)
|
108
|
+
|
109
|
+
except Exception as e:
|
110
|
+
error_result = self._handle_exception(
|
111
|
+
"process_webhook", e,
|
112
|
+
provider=request.provider if hasattr(request, 'provider') else None
|
113
|
+
)
|
114
|
+
|
115
|
+
return WebhookProcessingResult(
|
116
|
+
success=False,
|
117
|
+
provider=request.provider if hasattr(request, 'provider') else 'unknown',
|
118
|
+
error_message=error_result.message,
|
119
|
+
processed=False
|
120
|
+
)
|
121
|
+
|
122
|
+
def _validate_nowpayments_webhook(self, request: WebhookValidationRequest) -> ServiceOperationResult:
|
123
|
+
"""Validate NowPayments webhook."""
|
124
|
+
try:
|
125
|
+
# Validate payload structure
|
126
|
+
try:
|
127
|
+
webhook_data = NowPaymentsWebhook(**request.payload)
|
128
|
+
except Exception as e:
|
129
|
+
return self._create_error_result(
|
130
|
+
f"Invalid NowPayments webhook payload: {e}",
|
131
|
+
"invalid_payload"
|
132
|
+
)
|
133
|
+
|
134
|
+
# Validate signature if provided
|
135
|
+
signature_valid = True
|
136
|
+
if request.signature:
|
137
|
+
signature_result = self._validate_nowpayments_signature(request)
|
138
|
+
signature_valid = signature_result.success
|
139
|
+
|
140
|
+
if not signature_valid:
|
141
|
+
return self._create_error_result(
|
142
|
+
"Invalid webhook signature",
|
143
|
+
"invalid_signature"
|
144
|
+
)
|
145
|
+
|
146
|
+
return self._create_success_result(
|
147
|
+
"NowPayments webhook validated successfully",
|
148
|
+
{
|
149
|
+
'provider': 'nowpayments',
|
150
|
+
'payment_id': webhook_data.payment_id,
|
151
|
+
'status': webhook_data.payment_status,
|
152
|
+
'signature_valid': signature_valid,
|
153
|
+
'parsed_data': webhook_data.model_dump()
|
154
|
+
}
|
155
|
+
)
|
156
|
+
|
157
|
+
except Exception as e:
|
158
|
+
return self._create_error_result(
|
159
|
+
f"NowPayments validation error: {e}",
|
160
|
+
"validation_error"
|
161
|
+
)
|
162
|
+
|
163
|
+
def _validate_nowpayments_signature(self, request: WebhookValidationRequest) -> ServiceOperationResult:
|
164
|
+
"""Validate NowPayments webhook signature."""
|
165
|
+
try:
|
166
|
+
# Get secret key from Constance settings
|
167
|
+
constance_settings = self.config_service.get_constance_settings()
|
168
|
+
provider_keys = constance_settings.get_provider_keys('nowpayments')
|
169
|
+
secret_key = provider_keys.get('ipn_secret', '')
|
170
|
+
|
171
|
+
if not secret_key:
|
172
|
+
return self._create_error_result(
|
173
|
+
"NowPayments IPN secret not configured",
|
174
|
+
"missing_secret_key"
|
175
|
+
)
|
176
|
+
|
177
|
+
# Create signature validator
|
178
|
+
payload_string = json.dumps(request.payload, separators=(',', ':'), sort_keys=True)
|
179
|
+
|
180
|
+
signature_validator = WebhookSignature(
|
181
|
+
provider='nowpayments',
|
182
|
+
signature=request.signature,
|
183
|
+
payload=payload_string,
|
184
|
+
secret_key=secret_key,
|
185
|
+
algorithm='sha512'
|
186
|
+
)
|
187
|
+
|
188
|
+
is_valid = signature_validator.validate_signature()
|
189
|
+
|
190
|
+
if is_valid:
|
191
|
+
return self._create_success_result("Signature is valid")
|
192
|
+
else:
|
193
|
+
return self._create_error_result(
|
194
|
+
"Invalid signature",
|
195
|
+
"invalid_signature"
|
196
|
+
)
|
197
|
+
|
198
|
+
except Exception as e:
|
199
|
+
return self._create_error_result(
|
200
|
+
f"Signature validation error: {e}",
|
201
|
+
"signature_validation_error"
|
202
|
+
)
|
203
|
+
|
204
|
+
def _process_nowpayments_webhook(self, request: WebhookValidationRequest) -> WebhookProcessingResult:
|
205
|
+
"""Process NowPayments webhook."""
|
206
|
+
try:
|
207
|
+
# Parse webhook data
|
208
|
+
webhook_data = NowPaymentsWebhook(**request.payload)
|
209
|
+
|
210
|
+
# Find payment by provider payment ID
|
211
|
+
try:
|
212
|
+
payment = UniversalPayment.objects.get(
|
213
|
+
provider_payment_id=webhook_data.payment_id
|
214
|
+
)
|
215
|
+
except UniversalPayment.DoesNotExist:
|
216
|
+
return WebhookProcessingResult(
|
217
|
+
success=False,
|
218
|
+
provider='nowpayments',
|
219
|
+
error_message=f"Payment not found: {webhook_data.payment_id}",
|
220
|
+
processed=False
|
221
|
+
)
|
222
|
+
|
223
|
+
# Store original status
|
224
|
+
original_status = payment.status
|
225
|
+
actions_taken = []
|
226
|
+
|
227
|
+
# Convert NowPayments status to universal status
|
228
|
+
new_status = webhook_data.to_universal_status()
|
229
|
+
|
230
|
+
# Process status change
|
231
|
+
def process_webhook_transaction():
|
232
|
+
nonlocal actions_taken
|
233
|
+
|
234
|
+
if new_status == 'completed' and original_status != 'completed':
|
235
|
+
# Mark payment as completed
|
236
|
+
success = payment.mark_completed(
|
237
|
+
actual_amount_usd=float(webhook_data.actually_paid) if webhook_data.actually_paid else None,
|
238
|
+
transaction_hash=webhook_data.txn_id
|
239
|
+
)
|
240
|
+
|
241
|
+
if success:
|
242
|
+
actions_taken.append("payment_completed")
|
243
|
+
|
244
|
+
# Add funds to user balance
|
245
|
+
balance_result = self.balance_service.add_funds(
|
246
|
+
user_id=payment.user_id,
|
247
|
+
amount=payment.amount_usd,
|
248
|
+
description=f"Payment completed: {payment.id}",
|
249
|
+
payment_id=str(payment.id)
|
250
|
+
)
|
251
|
+
|
252
|
+
if balance_result.success:
|
253
|
+
actions_taken.append("balance_updated")
|
254
|
+
else:
|
255
|
+
self.logger.error("Failed to update balance", extra={
|
256
|
+
'payment_id': str(payment.id),
|
257
|
+
'user_id': payment.user_id,
|
258
|
+
'error': balance_result.message
|
259
|
+
})
|
260
|
+
|
261
|
+
elif new_status == 'failed' and original_status not in ['failed', 'cancelled']:
|
262
|
+
# Mark payment as failed
|
263
|
+
success = payment.mark_failed(
|
264
|
+
reason=f"Provider status: {webhook_data.payment_status}",
|
265
|
+
error_code=webhook_data.payment_status
|
266
|
+
)
|
267
|
+
|
268
|
+
if success:
|
269
|
+
actions_taken.append("payment_failed")
|
270
|
+
|
271
|
+
elif new_status == 'expired' and original_status not in ['failed', 'cancelled', 'expired']:
|
272
|
+
# Mark payment as failed due to expiration
|
273
|
+
success = payment.mark_failed(
|
274
|
+
reason="Payment expired",
|
275
|
+
error_code="expired"
|
276
|
+
)
|
277
|
+
|
278
|
+
if success:
|
279
|
+
actions_taken.append("payment_expired")
|
280
|
+
|
281
|
+
# Update provider-specific fields
|
282
|
+
if webhook_data.txn_id and not payment.transaction_hash:
|
283
|
+
payment.transaction_hash = webhook_data.txn_id
|
284
|
+
payment.save(update_fields=['transaction_hash', 'updated_at'])
|
285
|
+
actions_taken.append("transaction_hash_updated")
|
286
|
+
|
287
|
+
return True
|
288
|
+
|
289
|
+
# Execute in transaction
|
290
|
+
self._execute_with_transaction(process_webhook_transaction)
|
291
|
+
|
292
|
+
# Refresh payment
|
293
|
+
payment.refresh_from_db()
|
294
|
+
|
295
|
+
self._log_operation(
|
296
|
+
"process_nowpayments_webhook",
|
297
|
+
True,
|
298
|
+
payment_id=str(payment.id),
|
299
|
+
provider_payment_id=webhook_data.payment_id,
|
300
|
+
status_change=f"{original_status} -> {payment.status}",
|
301
|
+
actions_taken=actions_taken
|
302
|
+
)
|
303
|
+
|
304
|
+
return WebhookProcessingResult(
|
305
|
+
success=True,
|
306
|
+
provider='nowpayments',
|
307
|
+
payment_id=str(payment.id),
|
308
|
+
status_before=original_status,
|
309
|
+
status_after=payment.status,
|
310
|
+
actions_taken=actions_taken,
|
311
|
+
processed=True,
|
312
|
+
balance_updated='balance_updated' in actions_taken
|
313
|
+
)
|
314
|
+
|
315
|
+
except Exception as e:
|
316
|
+
error_result = self._handle_exception(
|
317
|
+
"process_nowpayments_webhook", e,
|
318
|
+
provider_payment_id=webhook_data.payment_id if 'webhook_data' in locals() else None
|
319
|
+
)
|
320
|
+
|
321
|
+
return WebhookProcessingResult(
|
322
|
+
success=False,
|
323
|
+
provider='nowpayments',
|
324
|
+
error_message=error_result.message,
|
325
|
+
processed=False
|
326
|
+
)
|
327
|
+
|
328
|
+
def get_webhook_stats(self, days: int = 30) -> ServiceOperationResult:
|
329
|
+
"""
|
330
|
+
Get webhook processing statistics.
|
331
|
+
|
332
|
+
Args:
|
333
|
+
days: Number of days to analyze
|
334
|
+
|
335
|
+
Returns:
|
336
|
+
ServiceOperationResult: Webhook statistics
|
337
|
+
"""
|
338
|
+
try:
|
339
|
+
# This would typically query a webhook log table
|
340
|
+
# For now, return basic stats from payments
|
341
|
+
from datetime import timedelta
|
342
|
+
|
343
|
+
since = timezone.now() - timedelta(days=days)
|
344
|
+
|
345
|
+
# Count payments updated recently (proxy for webhook activity)
|
346
|
+
recent_updates = UniversalPayment.objects.filter(
|
347
|
+
updated_at__gte=since
|
348
|
+
).exclude(
|
349
|
+
created_at=models.F('updated_at') # Exclude newly created payments
|
350
|
+
).count()
|
351
|
+
|
352
|
+
# Status distribution of recent updates
|
353
|
+
status_distribution = UniversalPayment.objects.filter(
|
354
|
+
updated_at__gte=since
|
355
|
+
).values('status').annotate(
|
356
|
+
count=models.Count('id')
|
357
|
+
).order_by('-count')
|
358
|
+
|
359
|
+
stats = {
|
360
|
+
'period_days': days,
|
361
|
+
'recent_payment_updates': recent_updates,
|
362
|
+
'status_distribution': list(status_distribution),
|
363
|
+
'generated_at': timezone.now().isoformat()
|
364
|
+
}
|
365
|
+
|
366
|
+
return self._create_success_result(
|
367
|
+
f"Webhook statistics for {days} days",
|
368
|
+
stats
|
369
|
+
)
|
370
|
+
|
371
|
+
except Exception as e:
|
372
|
+
return self._handle_exception("get_webhook_stats", e)
|
373
|
+
|
374
|
+
def health_check(self) -> ServiceOperationResult:
|
375
|
+
"""Perform webhook service health check."""
|
376
|
+
try:
|
377
|
+
# Check dependencies
|
378
|
+
payment_health = self.payment_service.health_check()
|
379
|
+
balance_health = self.balance_service.health_check()
|
380
|
+
|
381
|
+
# Check recent webhook activity (payments with provider_payment_id)
|
382
|
+
recent_webhooks = UniversalPayment.objects.filter(
|
383
|
+
provider_payment_id__isnull=False,
|
384
|
+
updated_at__gte=timezone.now() - timezone.timedelta(hours=1)
|
385
|
+
).count()
|
386
|
+
|
387
|
+
stats = {
|
388
|
+
'service_name': 'WebhookService',
|
389
|
+
'payment_service_healthy': payment_health.success,
|
390
|
+
'balance_service_healthy': balance_health.success,
|
391
|
+
'recent_webhook_activity': recent_webhooks,
|
392
|
+
'supported_providers': ['nowpayments']
|
393
|
+
}
|
394
|
+
|
395
|
+
overall_healthy = payment_health.success and balance_health.success
|
396
|
+
|
397
|
+
if overall_healthy:
|
398
|
+
return self._create_success_result(
|
399
|
+
"WebhookService is healthy",
|
400
|
+
stats
|
401
|
+
)
|
402
|
+
else:
|
403
|
+
return self._create_error_result(
|
404
|
+
"WebhookService has dependency issues",
|
405
|
+
"dependency_unhealthy",
|
406
|
+
stats
|
407
|
+
)
|
408
|
+
|
409
|
+
except Exception as e:
|
410
|
+
return self._handle_exception("health_check", e)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""
|
2
|
+
Integration utilities for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
External service integrations and development tools.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .ngrok_service import (
|
8
|
+
get_webhook_url_for_provider,
|
9
|
+
get_all_webhook_urls,
|
10
|
+
get_api_base_url,
|
11
|
+
is_ngrok_available,
|
12
|
+
)
|
13
|
+
from .providers_config import (
|
14
|
+
get_supported_providers,
|
15
|
+
get_webhook_provider_info,
|
16
|
+
get_all_providers_info,
|
17
|
+
is_provider_supported,
|
18
|
+
)
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
'get_webhook_url_for_provider',
|
22
|
+
'get_all_webhook_urls',
|
23
|
+
'get_api_base_url',
|
24
|
+
'is_ngrok_available',
|
25
|
+
'get_supported_providers',
|
26
|
+
'get_webhook_provider_info',
|
27
|
+
'get_all_providers_info',
|
28
|
+
'is_provider_supported',
|
29
|
+
]
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"""
|
2
|
+
Ngrok utilities for webhook development.
|
3
|
+
|
4
|
+
Simple helper functions using django_ngrok module.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict
|
8
|
+
from .providers_config import get_supported_providers
|
9
|
+
|
10
|
+
|
11
|
+
def get_webhook_url_for_provider(provider: str) -> str:
|
12
|
+
"""
|
13
|
+
Get webhook URL for specific provider.
|
14
|
+
|
15
|
+
Uses django_ngrok if available, otherwise localhost fallback.
|
16
|
+
"""
|
17
|
+
try:
|
18
|
+
from django_cfg.modules.django_ngrok import get_webhook_url
|
19
|
+
return get_webhook_url(f"{provider}/")
|
20
|
+
except ImportError:
|
21
|
+
return f"http://localhost:8000/api/webhooks/{provider}/"
|
22
|
+
|
23
|
+
|
24
|
+
def get_all_webhook_urls() -> Dict[str, str]:
|
25
|
+
"""Get webhook URLs for all supported providers."""
|
26
|
+
return {
|
27
|
+
provider: get_webhook_url_for_provider(provider)
|
28
|
+
for provider in get_supported_providers()
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
def get_api_base_url() -> str:
|
33
|
+
"""Get API base URL (ngrok tunnel or localhost)."""
|
34
|
+
try:
|
35
|
+
from django_cfg.modules.django_ngrok import get_api_url
|
36
|
+
return get_api_url()
|
37
|
+
except ImportError:
|
38
|
+
return "http://localhost:8000"
|
39
|
+
|
40
|
+
|
41
|
+
def is_ngrok_available() -> bool:
|
42
|
+
"""Check if ngrok tunnel is available."""
|
43
|
+
try:
|
44
|
+
from django_cfg.modules.django_ngrok import get_tunnel_url
|
45
|
+
return get_tunnel_url() is not None
|
46
|
+
except ImportError:
|
47
|
+
return False
|
@@ -0,0 +1,107 @@
|
|
1
|
+
"""
|
2
|
+
Dynamic webhook providers configuration.
|
3
|
+
|
4
|
+
Uses ProviderRegistry to get available providers dynamically.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, List
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from ..providers.registry import get_provider_registry
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class WebhookProviderInfo:
|
14
|
+
"""Webhook information for a provider."""
|
15
|
+
name: str
|
16
|
+
display_name: str
|
17
|
+
signature_header: str
|
18
|
+
signature_algorithm: str
|
19
|
+
content_type: str = 'application/json'
|
20
|
+
icon: str = '🔌'
|
21
|
+
|
22
|
+
|
23
|
+
# Provider webhook metadata (only what's needed for webhooks)
|
24
|
+
WEBHOOK_METADATA = {
|
25
|
+
'nowpayments': WebhookProviderInfo(
|
26
|
+
name='nowpayments',
|
27
|
+
display_name='NowPayments',
|
28
|
+
signature_header='x-nowpayments-sig',
|
29
|
+
signature_algorithm='HMAC-SHA512',
|
30
|
+
icon='💎'
|
31
|
+
),
|
32
|
+
# 'stripe': WebhookProviderInfo(
|
33
|
+
# name='stripe',
|
34
|
+
# display_name='Stripe',
|
35
|
+
# signature_header='stripe-signature',
|
36
|
+
# signature_algorithm='HMAC-SHA256'
|
37
|
+
# ),
|
38
|
+
# 'cryptapi': WebhookProviderInfo(
|
39
|
+
# name='cryptapi',
|
40
|
+
# display_name='CryptAPI',
|
41
|
+
# signature_header='x-cryptapi-signature',
|
42
|
+
# signature_algorithm='HMAC-SHA256'
|
43
|
+
# ),
|
44
|
+
# 'cryptomus': WebhookProviderInfo(
|
45
|
+
# name='cryptomus',
|
46
|
+
# display_name='Cryptomus',
|
47
|
+
# signature_header='sign',
|
48
|
+
# signature_algorithm='HMAC-SHA256'
|
49
|
+
# )
|
50
|
+
}
|
51
|
+
|
52
|
+
|
53
|
+
def get_supported_providers() -> List[str]:
|
54
|
+
"""Get list of supported providers from ProviderRegistry."""
|
55
|
+
try:
|
56
|
+
registry = get_provider_registry()
|
57
|
+
return registry.get_available_providers()
|
58
|
+
except Exception:
|
59
|
+
# Fallback to providers with webhook metadata
|
60
|
+
return list(WEBHOOK_METADATA.keys())
|
61
|
+
|
62
|
+
|
63
|
+
def get_webhook_provider_info(provider: str) -> WebhookProviderInfo:
|
64
|
+
"""Get webhook info for a specific provider."""
|
65
|
+
if provider not in WEBHOOK_METADATA:
|
66
|
+
# Default webhook info for unknown providers
|
67
|
+
return WebhookProviderInfo(
|
68
|
+
name=provider,
|
69
|
+
display_name=provider.title(),
|
70
|
+
signature_header='signature',
|
71
|
+
signature_algorithm='HMAC-SHA256',
|
72
|
+
icon='🔌'
|
73
|
+
)
|
74
|
+
return WEBHOOK_METADATA[provider]
|
75
|
+
|
76
|
+
|
77
|
+
def get_signature_header(provider: str) -> str:
|
78
|
+
"""Get signature header name for provider."""
|
79
|
+
return get_webhook_provider_info(provider).signature_header
|
80
|
+
|
81
|
+
|
82
|
+
def get_signature_algorithm(provider: str) -> str:
|
83
|
+
"""Get signature algorithm for provider."""
|
84
|
+
return get_webhook_provider_info(provider).signature_algorithm
|
85
|
+
|
86
|
+
|
87
|
+
def is_provider_supported(provider: str) -> bool:
|
88
|
+
"""Check if provider is supported (from registry)."""
|
89
|
+
return provider in get_supported_providers()
|
90
|
+
|
91
|
+
|
92
|
+
def get_all_providers_info() -> Dict[str, Dict[str, Any]]:
|
93
|
+
"""Get all providers information as dict."""
|
94
|
+
supported_providers = get_supported_providers()
|
95
|
+
|
96
|
+
return {
|
97
|
+
name: {
|
98
|
+
'name': info.name,
|
99
|
+
'display_name': info.display_name,
|
100
|
+
'signature_header': info.signature_header,
|
101
|
+
'signature_algorithm': info.signature_algorithm,
|
102
|
+
'content_type': info.content_type,
|
103
|
+
'icon': info.icon
|
104
|
+
}
|
105
|
+
for name in supported_providers
|
106
|
+
for info in [get_webhook_provider_info(name)]
|
107
|
+
}
|
@@ -1,22 +1,17 @@
|
|
1
1
|
"""
|
2
|
-
Payment
|
2
|
+
Payment providers for the Universal Payment System v2.0.
|
3
3
|
|
4
|
-
|
4
|
+
Provider implementations with unified interface and Pydantic validation.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from .base import
|
8
|
-
from .
|
9
|
-
from .
|
10
|
-
from .cryptapi import CryptAPIProvider, CryptAPIConfig
|
11
|
-
from .cryptomus import CryptomusProvider, CryptomusConfig
|
7
|
+
from .base import BaseProvider
|
8
|
+
from .nowpayments import NowPaymentsProvider
|
9
|
+
from .registry import ProviderRegistry, get_provider_registry, initialize_providers
|
12
10
|
|
13
11
|
__all__ = [
|
14
|
-
'
|
15
|
-
'ProviderRegistry',
|
12
|
+
'BaseProvider',
|
16
13
|
'NowPaymentsProvider',
|
17
|
-
'
|
18
|
-
'
|
19
|
-
'
|
20
|
-
'CryptomusProvider',
|
21
|
-
'CryptomusConfig',
|
14
|
+
'ProviderRegistry',
|
15
|
+
'get_provider_registry',
|
16
|
+
'initialize_providers',
|
22
17
|
]
|