django-cfg 1.2.27__py3-none-any.whl → 1.2.31__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 (138) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/__init__.py +3 -2
  3. django_cfg/apps/payments/admin/balance_admin.py +18 -18
  4. django_cfg/apps/payments/admin/currencies_admin.py +319 -131
  5. django_cfg/apps/payments/admin/payments_admin.py +15 -4
  6. django_cfg/apps/payments/config/module.py +2 -2
  7. django_cfg/apps/payments/config/utils.py +2 -2
  8. django_cfg/apps/payments/decorators.py +2 -2
  9. django_cfg/apps/payments/management/commands/README.md +95 -127
  10. django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
  11. django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
  12. django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
  13. django_cfg/apps/payments/managers/__init__.py +3 -2
  14. django_cfg/apps/payments/managers/balance_manager.py +2 -2
  15. django_cfg/apps/payments/managers/currency_manager.py +272 -49
  16. django_cfg/apps/payments/managers/payment_manager.py +161 -13
  17. django_cfg/apps/payments/middleware/api_access.py +2 -2
  18. django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
  19. django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
  20. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
  21. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
  22. django_cfg/apps/payments/models/__init__.py +3 -2
  23. django_cfg/apps/payments/models/currencies.py +187 -71
  24. django_cfg/apps/payments/models/payments.py +3 -2
  25. django_cfg/apps/payments/serializers/__init__.py +3 -2
  26. django_cfg/apps/payments/serializers/currencies.py +20 -12
  27. django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
  28. django_cfg/apps/payments/services/core/balance_service.py +2 -2
  29. django_cfg/apps/payments/services/core/fallback_service.py +2 -2
  30. django_cfg/apps/payments/services/core/payment_service.py +3 -6
  31. django_cfg/apps/payments/services/core/subscription_service.py +4 -7
  32. django_cfg/apps/payments/services/internal_types.py +171 -7
  33. django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
  34. django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
  35. django_cfg/apps/payments/services/providers/base.py +144 -43
  36. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
  37. django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
  38. django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
  39. django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
  40. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
  41. django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
  42. django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
  43. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
  44. django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
  45. django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
  46. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
  47. django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
  48. django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
  49. django_cfg/apps/payments/services/providers/registry.py +294 -11
  50. django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
  51. django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
  52. django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
  53. django_cfg/apps/payments/services/security/error_handler.py +6 -8
  54. django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
  55. django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
  56. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  57. django_cfg/apps/payments/signals/payment_signals.py +11 -5
  58. django_cfg/apps/payments/signals/subscription_signals.py +2 -2
  59. django_cfg/apps/payments/static/payments/css/payments.css +340 -0
  60. django_cfg/apps/payments/static/payments/js/notifications.js +202 -0
  61. django_cfg/apps/payments/static/payments/js/payment-utils.js +318 -0
  62. django_cfg/apps/payments/static/payments/js/theme.js +86 -0
  63. django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
  64. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
  65. django_cfg/apps/payments/templates/payments/base.html +182 -0
  66. django_cfg/apps/payments/templates/payments/components/payment_card.html +201 -0
  67. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +109 -0
  68. django_cfg/apps/payments/templates/payments/components/progress_bar.html +43 -0
  69. django_cfg/apps/payments/templates/payments/components/provider_stats.html +40 -0
  70. django_cfg/apps/payments/templates/payments/components/status_badge.html +34 -0
  71. django_cfg/apps/payments/templates/payments/components/status_overview.html +148 -0
  72. django_cfg/apps/payments/templates/payments/dashboard.html +258 -0
  73. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
  74. django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
  75. django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
  76. django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
  77. django_cfg/apps/payments/templates/payments/stats.html +261 -0
  78. django_cfg/apps/payments/templates/payments/test.html +213 -0
  79. django_cfg/apps/payments/templatetags/__init__.py +1 -0
  80. django_cfg/apps/payments/templatetags/payments_tags.py +315 -0
  81. django_cfg/apps/payments/urls.py +3 -1
  82. django_cfg/apps/payments/urls_admin.py +58 -0
  83. django_cfg/apps/payments/utils/__init__.py +1 -3
  84. django_cfg/apps/payments/utils/billing_utils.py +2 -2
  85. django_cfg/apps/payments/utils/config_utils.py +2 -8
  86. django_cfg/apps/payments/utils/validation_utils.py +2 -2
  87. django_cfg/apps/payments/views/__init__.py +3 -2
  88. django_cfg/apps/payments/views/currency_views.py +31 -20
  89. django_cfg/apps/payments/views/payment_views.py +2 -2
  90. django_cfg/apps/payments/views/templates/__init__.py +25 -0
  91. django_cfg/apps/payments/views/templates/ajax.py +451 -0
  92. django_cfg/apps/payments/views/templates/base.py +212 -0
  93. django_cfg/apps/payments/views/templates/dashboard.py +60 -0
  94. django_cfg/apps/payments/views/templates/payment_detail.py +102 -0
  95. django_cfg/apps/payments/views/templates/payment_management.py +158 -0
  96. django_cfg/apps/payments/views/templates/qr_code.py +174 -0
  97. django_cfg/apps/payments/views/templates/stats.py +244 -0
  98. django_cfg/apps/payments/views/templates/utils.py +181 -0
  99. django_cfg/apps/payments/views/webhook_views.py +2 -2
  100. django_cfg/apps/payments/viewsets.py +3 -2
  101. django_cfg/apps/tasks/urls.py +0 -2
  102. django_cfg/apps/tasks/urls_admin.py +14 -0
  103. django_cfg/apps/urls.py +6 -3
  104. django_cfg/core/config.py +35 -0
  105. django_cfg/models/payments.py +2 -8
  106. django_cfg/modules/django_currency/__init__.py +16 -11
  107. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  108. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  109. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  110. django_cfg/modules/django_currency/core/__init__.py +1 -7
  111. django_cfg/modules/django_currency/core/converter.py +18 -23
  112. django_cfg/modules/django_currency/core/models.py +122 -11
  113. django_cfg/modules/django_currency/database/__init__.py +4 -4
  114. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  115. django_cfg/modules/django_unfold/dashboard.py +7 -2
  116. django_cfg/registry/core.py +1 -0
  117. django_cfg/template_archive/.gitignore +1 -0
  118. django_cfg/template_archive/django_sample.zip +0 -0
  119. django_cfg/templates/admin/components/action_grid.html +9 -9
  120. django_cfg/templates/admin/components/metric_card.html +5 -5
  121. django_cfg/templates/admin/components/status_badge.html +2 -2
  122. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  123. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  124. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  125. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  126. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/METADATA +13 -18
  127. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/RECORD +130 -83
  128. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  129. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  130. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  131. django_cfg/apps/payments/services/providers/cryptomus.py +0 -310
  132. django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
  133. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  134. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  135. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  136. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
  137. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
  138. {django_cfg-1.2.27.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,190 @@
1
+ """
2
+ Provider-specific currency and network models.
3
+
4
+ These models are ONLY for provider-specific currency data,
5
+ NOT for universal service communication (those are in internal_types.py).
6
+ """
7
+
8
+ from pydantic import BaseModel, Field, ConfigDict, field_validator
9
+ from decimal import Decimal
10
+ from typing import List, Dict, Optional, Any
11
+ from enum import Enum
12
+
13
+
14
+ class CurrencyType(str, Enum):
15
+ """Currency type enumeration."""
16
+ FIAT = "fiat"
17
+ CRYPTO = "crypto"
18
+
19
+
20
+ class NetworkType(str, Enum):
21
+ """Network type enumeration."""
22
+ MAINNET = "mainnet"
23
+ TESTNET = "testnet"
24
+ LAYER2 = "layer2"
25
+ SIDECHAIN = "sidechain"
26
+
27
+
28
+ class CurrencyInfo(BaseModel):
29
+ """Information about a currency from provider API."""
30
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
31
+
32
+ code: str = Field(min_length=2, max_length=10, description="Currency code (BTC, ETH, USD)")
33
+ name: str = Field(min_length=1, max_length=100, description="Full currency name")
34
+ symbol: Optional[str] = Field(None, max_length=10, description="Currency symbol")
35
+ currency_type: CurrencyType = Field(description="Type of currency")
36
+
37
+ # Provider-specific data
38
+ min_amount: Optional[Decimal] = Field(None, ge=0, description="Minimum payment amount")
39
+ max_amount: Optional[Decimal] = Field(None, ge=0, description="Maximum payment amount")
40
+ precision: Optional[int] = Field(None, ge=0, le=18, description="Decimal precision")
41
+
42
+ # Exchange rate info
43
+ usd_rate: Optional[Decimal] = Field(None, gt=0, description="Rate to USD")
44
+ rate_updated_at: Optional[str] = Field(None, description="Rate update timestamp")
45
+
46
+ # Provider metadata
47
+ provider_metadata: Dict[str, Any] = Field(default_factory=dict, description="Provider-specific data")
48
+
49
+ @field_validator('code')
50
+ @classmethod
51
+ def code_must_be_uppercase(cls, v):
52
+ return v.upper() if v else v
53
+
54
+ @field_validator('max_amount')
55
+ @classmethod
56
+ def max_amount_must_be_greater_than_min(cls, v, info):
57
+ if v is not None and 'min_amount' in info.data and info.data['min_amount'] is not None:
58
+ if v <= info.data['min_amount']:
59
+ raise ValueError('max_amount must be greater than min_amount')
60
+ return v
61
+
62
+
63
+ class NetworkInfo(BaseModel):
64
+ """Information about a blockchain network from provider API."""
65
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
66
+
67
+ code: str = Field(min_length=1, max_length=20, description="Network code")
68
+ name: str = Field(min_length=1, max_length=50, description="Network display name")
69
+ network_type: Optional[NetworkType] = Field(None, description="Type of network")
70
+
71
+ # Network-specific settings
72
+ confirmation_blocks: int = Field(default=1, ge=0, description="Required confirmations")
73
+ min_amount: Optional[Decimal] = Field(None, ge=0, description="Minimum amount for this network")
74
+ max_amount: Optional[Decimal] = Field(None, ge=0, description="Maximum amount for this network")
75
+
76
+ # Fee information
77
+ base_fee: Optional[Decimal] = Field(None, ge=0, description="Base network fee")
78
+ fee_percentage: Optional[Decimal] = Field(None, ge=0, le=100, description="Fee percentage")
79
+
80
+ # Network status
81
+ is_active: bool = Field(default=True, description="Whether network is active")
82
+ is_maintenance: bool = Field(default=False, description="Whether network is in maintenance")
83
+
84
+ # Provider metadata
85
+ provider_metadata: Dict[str, Any] = Field(default_factory=dict, description="Provider-specific data")
86
+
87
+ @field_validator('code')
88
+ @classmethod
89
+ def code_must_be_lowercase(cls, v):
90
+ return v.lower() if v else v
91
+
92
+
93
+ class ProviderCurrencyResponse(BaseModel):
94
+ """Response from provider API for supported currencies."""
95
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
96
+
97
+ success: bool = Field(description="Whether request was successful")
98
+ currencies: List[CurrencyInfo] = Field(default_factory=list, description="List of supported currencies")
99
+ total_count: Optional[int] = Field(None, ge=0, description="Total number of currencies")
100
+
101
+ # Error information
102
+ error_code: Optional[str] = Field(None, description="Provider error code")
103
+ error_message: Optional[str] = Field(None, description="Error message if failed")
104
+
105
+ # Request metadata
106
+ provider_name: Optional[str] = Field(None, description="Provider name")
107
+ request_timestamp: Optional[str] = Field(None, description="Request timestamp")
108
+ cache_ttl: Optional[int] = Field(None, ge=0, description="Cache TTL in seconds")
109
+
110
+ @field_validator('currencies')
111
+ @classmethod
112
+ def validate_currency_codes_unique(cls, v):
113
+ codes = [currency.code for currency in v]
114
+ if len(codes) != len(set(codes)):
115
+ raise ValueError('Currency codes must be unique')
116
+ return v
117
+
118
+
119
+ class ProviderNetworkResponse(BaseModel):
120
+ """Response from provider API for supported networks."""
121
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
122
+
123
+ success: bool = Field(description="Whether request was successful")
124
+ networks: Dict[str, List[NetworkInfo]] = Field(
125
+ default_factory=dict,
126
+ description="Networks grouped by currency code"
127
+ )
128
+
129
+ # Error information
130
+ error_code: Optional[str] = Field(None, description="Provider error code")
131
+ error_message: Optional[str] = Field(None, description="Error message if failed")
132
+
133
+ # Request metadata
134
+ provider_name: Optional[str] = Field(None, description="Provider name")
135
+ request_timestamp: Optional[str] = Field(None, description="Request timestamp")
136
+ cache_ttl: Optional[int] = Field(None, ge=0, description="Cache TTL in seconds")
137
+
138
+ @field_validator('networks')
139
+ @classmethod
140
+ def validate_network_structure(cls, v):
141
+ for currency_code, networks in v.items():
142
+ if not currency_code.isupper():
143
+ raise ValueError(f'Currency code {currency_code} must be uppercase')
144
+
145
+ network_codes = [network.code for network in networks]
146
+ if len(network_codes) != len(set(network_codes)):
147
+ raise ValueError(f'Network codes must be unique for currency {currency_code}')
148
+ return v
149
+
150
+
151
+ class CurrencyNetworkMapping(BaseModel):
152
+ """Mapping of currencies to their supported networks."""
153
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
154
+
155
+ provider_name: str = Field(description="Provider name")
156
+ mapping: Dict[str, List[str]] = Field(
157
+ default_factory=dict,
158
+ description="Currency code -> List of network codes"
159
+ )
160
+ last_updated: Optional[str] = Field(None, description="Last update timestamp")
161
+
162
+ # Cache information
163
+ cache_key: Optional[str] = Field(None, description="Cache key for this mapping")
164
+ ttl_seconds: Optional[int] = Field(None, ge=0, description="TTL for caching")
165
+
166
+ @field_validator('mapping')
167
+ @classmethod
168
+ def validate_mapping_structure(cls, v):
169
+ for currency_code, network_codes in v.items():
170
+ if not currency_code.isupper():
171
+ raise ValueError(f'Currency code {currency_code} must be uppercase')
172
+
173
+ if len(network_codes) != len(set(network_codes)):
174
+ raise ValueError(f'Network codes must be unique for currency {currency_code}')
175
+ return v
176
+
177
+ def get_networks_for_currency(self, currency_code: str) -> List[str]:
178
+ """Get supported networks for a specific currency."""
179
+ return self.mapping.get(currency_code.upper(), [])
180
+
181
+ def get_all_currencies(self) -> List[str]:
182
+ """Get all supported currency codes."""
183
+ return list(self.mapping.keys())
184
+
185
+ def get_all_networks(self) -> List[str]:
186
+ """Get all unique network codes."""
187
+ all_networks = []
188
+ for networks in self.mapping.values():
189
+ all_networks.extend(networks)
190
+ return list(set(all_networks))
@@ -0,0 +1,4 @@
1
+ from .provider import NowPaymentsProvider
2
+ from .models import NowPaymentsConfig, NowPaymentsCurrency, NowPaymentsNetwork
3
+
4
+ __all__ = ['NowPaymentsProvider', 'NowPaymentsConfig', 'NowPaymentsCurrency', 'NowPaymentsNetwork']
@@ -0,0 +1,196 @@
1
+ from pydantic import BaseModel, Field, ConfigDict, field_validator
2
+ from typing import Optional, List
3
+ from decimal import Decimal
4
+
5
+ from ...internal_types import ProviderConfig
6
+
7
+
8
+ class NowPaymentsConfig(ProviderConfig):
9
+ """NowPayments provider configuration with Pydantic v2."""
10
+
11
+ ipn_secret: Optional[str] = Field(default=None, description="IPN secret for webhook validation")
12
+ callback_url: Optional[str] = Field(default=None, description="Webhook callback URL")
13
+ success_url: Optional[str] = Field(default=None, description="Payment success redirect URL")
14
+ cancel_url: Optional[str] = Field(default=None, description="Payment cancel redirect URL")
15
+
16
+
17
+
18
+
19
+ class NowPaymentsCurrency(BaseModel):
20
+ """NowPayments full currency model from /v1/full-currencies."""
21
+ model_config = ConfigDict(validate_assignment=True, extra="allow")
22
+
23
+ id: int = Field(..., description="Currency ID")
24
+ code: str = Field(..., description="Currency code (e.g., BTC, USDTERC20)")
25
+ name: str = Field(..., description="Full currency name")
26
+ enable: bool = Field(..., description="Currency availability")
27
+ wallet_regex: Optional[str] = Field(None, description="Wallet address regex")
28
+ priority: int = Field(..., description="Currency priority")
29
+ extra_id_exists: bool = Field(..., description="Whether extra ID is required")
30
+ extra_id_regex: Optional[str] = Field(None, description="Extra ID regex")
31
+ logo_url: str = Field(..., description="Currency logo URL")
32
+ track: bool = Field(..., description="Track transactions")
33
+ cg_id: Optional[str] = Field(None, description="CoinGecko ID")
34
+ is_maxlimit: bool = Field(..., description="Has max limit")
35
+ network: Optional[str] = Field(None, description="Blockchain network")
36
+ smart_contract: Optional[str] = Field(None, description="Smart contract address")
37
+ network_precision: Optional[str] = Field(None, description="Network precision")
38
+ explorer_link_hash: Optional[str] = Field(None, description="Explorer link")
39
+ precision: int = Field(..., description="Currency precision")
40
+ ticker: Optional[str] = Field(None, description="Ticker symbol")
41
+ is_defi: bool = Field(..., description="Is DeFi token")
42
+ is_popular: bool = Field(..., description="Is popular currency")
43
+ is_stable: bool = Field(..., description="Is stablecoin")
44
+ available_for_to_conversion: bool = Field(..., description="Available for conversion")
45
+ trust_wallet_id: Optional[str] = Field(None, description="Trust Wallet ID")
46
+ created_at: str = Field(..., description="Creation timestamp")
47
+ updated_at: str = Field(..., description="Update timestamp")
48
+ available_for_payment: bool = Field(..., description="Available for payment")
49
+ available_for_payout: bool = Field(..., description="Available for payout")
50
+ extra_id_optional: bool = Field(..., description="Extra ID is optional")
51
+
52
+
53
+ class NowPaymentsNetwork(BaseModel):
54
+ """NowPayments specific network model."""
55
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
56
+
57
+ code: str = Field(..., description="Network code")
58
+ name: str = Field(..., description="Network display name")
59
+ currency: str = Field(..., description="Currency this network belongs to")
60
+ confirmations: int = Field(1, description="Required confirmations")
61
+
62
+
63
+ class NowPaymentsPaymentRequest(BaseModel):
64
+ """NowPayments payment creation request."""
65
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
66
+
67
+ price_amount: float = Field(..., description="Payment amount")
68
+ price_currency: str = Field(..., description="Price currency (usually USD)")
69
+ pay_currency: str = Field(..., description="Payment currency (crypto)")
70
+ order_id: str = Field(..., description="Unique order identifier")
71
+ order_description: Optional[str] = Field(None, description="Order description")
72
+ success_url: Optional[str] = Field(None, description="Success redirect URL")
73
+ cancel_url: Optional[str] = Field(None, description="Cancel redirect URL")
74
+ ipn_callback_url: Optional[str] = Field(None, description="IPN callback URL")
75
+
76
+
77
+ class NowPaymentsPaymentResponse(BaseModel):
78
+ """NowPayments payment creation response."""
79
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
80
+
81
+ payment_id: str = Field(..., description="Payment ID")
82
+ payment_status: str = Field(..., description="Payment status")
83
+ pay_address: str = Field(..., description="Payment address")
84
+ price_amount: float = Field(..., description="Price amount")
85
+ price_currency: str = Field(..., description="Price currency")
86
+ pay_amount: float = Field(..., description="Payment amount")
87
+ pay_currency: str = Field(..., description="Payment currency")
88
+ order_id: str = Field(..., description="Order ID")
89
+ order_description: Optional[str] = Field(None, description="Order description")
90
+ invoice_url: Optional[str] = Field(None, description="Payment page URL")
91
+ success_url: Optional[str] = Field(None, description="Success URL")
92
+ cancel_url: Optional[str] = Field(None, description="Cancel URL")
93
+ created_at: str = Field(..., description="Creation timestamp")
94
+ updated_at: str = Field(..., description="Update timestamp")
95
+
96
+
97
+ class NowPaymentsWebhook(BaseModel):
98
+ """NowPayments webhook/IPN data."""
99
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
100
+
101
+ payment_id: str = Field(..., description="Payment ID")
102
+ payment_status: str = Field(..., description="Payment status")
103
+ pay_address: str = Field(..., description="Payment address")
104
+ price_amount: float = Field(..., description="Price amount")
105
+ price_currency: str = Field(..., description="Price currency")
106
+ pay_amount: float = Field(..., description="Payment amount")
107
+ pay_currency: str = Field(..., description="Payment currency")
108
+ order_id: str = Field(..., description="Order ID")
109
+ order_description: Optional[str] = Field(None, description="Order description")
110
+ outcome_amount: Optional[float] = Field(None, description="Outcome amount")
111
+ outcome_currency: Optional[str] = Field(None, description="Outcome currency")
112
+ actually_paid: Optional[float] = Field(None, description="Actually paid amount")
113
+ txid: Optional[str] = Field(None, description="Transaction ID")
114
+ created_at: str = Field(..., description="Creation timestamp")
115
+ updated_at: str = Field(..., description="Update timestamp")
116
+
117
+
118
+ class NowPaymentsStatusResponse(BaseModel):
119
+ """NowPayments payment status response."""
120
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
121
+
122
+ payment_id: str = Field(..., description="Payment ID")
123
+ payment_status: str = Field(..., description="Payment status")
124
+ pay_address: str = Field(..., description="Payment address")
125
+ price_amount: float = Field(..., description="Price amount")
126
+ price_currency: str = Field(..., description="Price currency")
127
+ pay_amount: float = Field(..., description="Payment amount")
128
+ pay_currency: str = Field(..., description="Payment currency")
129
+ order_id: str = Field(..., description="Order ID")
130
+ order_description: Optional[str] = Field(None, description="Order description")
131
+ outcome_amount: Optional[float] = Field(None, description="Outcome amount")
132
+ outcome_currency: Optional[str] = Field(None, description="Outcome currency")
133
+ actually_paid: Optional[float] = Field(None, description="Actually paid amount")
134
+ created_at: str = Field(..., description="Creation timestamp")
135
+ updated_at: str = Field(..., description="Update timestamp")
136
+
137
+
138
+ class NowPaymentsCurrenciesResponse(BaseModel):
139
+ """NowPayments supported currencies response."""
140
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
141
+
142
+ currencies: List[str] = Field(..., description="List of supported currency codes")
143
+
144
+
145
+ class NowPaymentsFullCurrenciesResponse(BaseModel):
146
+ """NowPayments full currencies response from /v1/full-currencies."""
147
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
148
+
149
+ currencies: List[NowPaymentsCurrency] = Field(..., description="List of full currency data")
150
+
151
+
152
+ class NowPaymentsMinAmountResponse(BaseModel):
153
+ """NowPayments minimum amount response."""
154
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
155
+
156
+ currency_from: str = Field(..., description="Source currency")
157
+ currency_to: str = Field(..., description="Target currency")
158
+ min_amount: float = Field(..., description="Minimum payment amount")
159
+
160
+
161
+ class NowPaymentsEstimateResponse(BaseModel):
162
+ """NowPayments payment estimate response."""
163
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
164
+
165
+ currency_from: str = Field(..., description="Source currency")
166
+ currency_to: str = Field(..., description="Target currency")
167
+ amount_from: float = Field(..., description="Source amount")
168
+ estimated_amount: float = Field(..., description="Estimated target amount")
169
+ fee_amount: Optional[float] = Field(None, description="Fee amount")
170
+
171
+
172
+ class NowPaymentsStatusInfo(BaseModel):
173
+ """NowPayments API status response."""
174
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
175
+
176
+ message: str = Field(..., description="Status message")
177
+ uptime: Optional[float] = Field(None, description="API uptime percentage")
178
+
179
+
180
+ # =============================================================================
181
+ # MONITORING & HEALTH CHECK MODELS
182
+ # =============================================================================
183
+
184
+ class NowPaymentsStatusResponse(BaseModel):
185
+ """NowPayments /v1/status response schema for health checks."""
186
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
187
+
188
+ message: str = Field(..., description="Status message")
189
+
190
+ @field_validator('message')
191
+ @classmethod
192
+ def validate_message_ok(cls, v):
193
+ """Validate that message is OK."""
194
+ if v.upper() != 'OK':
195
+ raise ValueError(f"Expected message 'OK', got '{v}'")
196
+ return v