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.
Files changed (264) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {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
+ )