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