django-cfg 1.3.5__py3-none-any.whl → 1.3.9__py3-none-any.whl

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