django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,20 +1,28 @@
1
1
  """
2
- Payment models for the universal payments system.
2
+ Payment models for the Universal Payment System v2.0.
3
+
4
+ Core payment model with simplified architecture focused on NowPayments.
3
5
  """
4
6
 
7
+ from decimal import Decimal
5
8
  from django.db import models
6
9
  from django.contrib.auth import get_user_model
7
- from django.core.validators import MinValueValidator
10
+ from django.core.validators import MinValueValidator, MaxValueValidator
8
11
  from django.core.exceptions import ValidationError
9
12
  from django.utils import timezone
10
13
  from .base import UUIDTimestampedModel
14
+ from .currencies import Currency, Network
11
15
 
12
16
  User = get_user_model()
13
17
 
14
18
 
15
-
16
19
  class UniversalPayment(UUIDTimestampedModel):
17
- """Universal payment model for all providers."""
20
+ """
21
+ Universal payment model supporting all providers.
22
+
23
+ Simplified v2.0 architecture focused on NowPayments with extensible design.
24
+ Uses float for USD amounts as per requirements for performance and API compatibility.
25
+ """
18
26
 
19
27
  class PaymentStatus(models.TextChoices):
20
28
  PENDING = "pending", "Pending"
@@ -28,384 +36,327 @@ class UniversalPayment(UUIDTimestampedModel):
28
36
 
29
37
  class PaymentProvider(models.TextChoices):
30
38
  NOWPAYMENTS = "nowpayments", "NowPayments"
31
- CRYPTAPI = "cryptapi", "CryptAPI"
32
- CRYPTOMUS = "cryptomus", "Cryptomus"
33
- STRIPE = "stripe", "Stripe"
34
- INTERNAL = "internal", "Internal"
39
+ # Future providers can be added here
40
+
41
+ @classmethod
42
+ def get_crypto_providers(cls):
43
+ """Get list of crypto provider values."""
44
+ return [cls.NOWPAYMENTS]
45
+
46
+ @classmethod
47
+ def is_crypto_provider(cls, provider_name: str) -> bool:
48
+ """Check if provider handles cryptocurrency."""
49
+ return provider_name in cls.get_crypto_providers()
35
50
 
51
+ # User and identification
36
52
  user = models.ForeignKey(
37
53
  User,
38
54
  on_delete=models.CASCADE,
39
- related_name='universal_payments',
40
- help_text="User who initiated this payment"
55
+ related_name='payments',
56
+ help_text="User who created this payment"
57
+ )
58
+
59
+ internal_payment_id = models.CharField(
60
+ max_length=100,
61
+ unique=True,
62
+ db_index=True,
63
+ help_text="Internal payment identifier"
41
64
  )
42
65
 
43
- # Financial data
66
+ # Financial information (USD as float per requirements)
44
67
  amount_usd = models.FloatField(
45
- validators=[MinValueValidator(1.0)],
46
- help_text="Payment amount in USD"
68
+ validators=[MinValueValidator(1.0), MaxValueValidator(50000.0)],
69
+ help_text="Payment amount in USD (float for performance)"
47
70
  )
48
- currency_code = models.CharField(
49
- max_length=10,
50
- help_text="Currency used for payment"
71
+
72
+ # Cryptocurrency information
73
+ currency = models.ForeignKey(
74
+ Currency,
75
+ on_delete=models.PROTECT,
76
+ related_name='payments',
77
+ help_text="Payment currency"
51
78
  )
52
79
 
53
- # Actual received amount (may differ from requested)
54
- actual_amount_usd = models.FloatField(
80
+ network = models.ForeignKey(
81
+ Network,
82
+ on_delete=models.PROTECT,
83
+ related_name='payments',
55
84
  null=True,
56
85
  blank=True,
57
- help_text="Actual received amount in USD"
86
+ help_text="Blockchain network (for crypto payments)"
58
87
  )
59
- actual_currency_code = models.CharField(
60
- max_length=10,
88
+
89
+ # Crypto amounts use Decimal for precision
90
+ pay_amount = models.DecimalField(
91
+ max_digits=20,
92
+ decimal_places=8,
61
93
  null=True,
62
94
  blank=True,
63
- help_text="Actual received currency"
95
+ help_text="Amount to pay in cryptocurrency (Decimal for precision)"
96
+ )
97
+
98
+ actual_amount_usd = models.FloatField(
99
+ null=True,
100
+ blank=True,
101
+ help_text="Actual amount received in USD"
64
102
  )
65
103
 
66
- # Fee information
67
104
  fee_amount_usd = models.FloatField(
68
105
  null=True,
69
106
  blank=True,
70
- validators=[MinValueValidator(0.0)],
71
107
  help_text="Fee amount in USD"
72
108
  )
73
109
 
74
- # Payment details
110
+ # Provider information
75
111
  provider = models.CharField(
76
112
  max_length=50,
77
113
  choices=PaymentProvider.choices,
114
+ default=PaymentProvider.NOWPAYMENTS,
78
115
  help_text="Payment provider"
79
116
  )
80
- status = models.CharField(
81
- max_length=20,
82
- choices=PaymentStatus.choices,
83
- default=PaymentStatus.PENDING,
84
- help_text="Payment status"
85
- )
86
117
 
87
- # Provider-specific fields
88
118
  provider_payment_id = models.CharField(
89
119
  max_length=255,
90
120
  null=True,
91
121
  blank=True,
92
- unique=True,
122
+ db_index=True,
93
123
  help_text="Provider's payment ID"
94
124
  )
95
- internal_payment_id = models.CharField(
96
- max_length=100,
97
- unique=True,
98
- help_text="Internal payment identifier"
125
+
126
+ # Payment details
127
+ status = models.CharField(
128
+ max_length=20,
129
+ choices=PaymentStatus.choices,
130
+ default=PaymentStatus.PENDING,
131
+ db_index=True,
132
+ help_text="Current payment status"
99
133
  )
100
134
 
101
- # Crypto payment specific
102
135
  pay_address = models.CharField(
103
- max_length=200,
136
+ max_length=255,
104
137
  null=True,
105
138
  blank=True,
106
139
  help_text="Cryptocurrency payment address"
107
140
  )
108
- pay_amount = models.FloatField(
109
- null=True,
110
- blank=True,
111
- help_text="Amount to pay in cryptocurrency"
112
- )
113
- network = models.CharField(
114
- max_length=50,
141
+
142
+ payment_url = models.URLField(
115
143
  null=True,
116
144
  blank=True,
117
- help_text="Blockchain network (mainnet, testnet, etc.)"
145
+ help_text="Payment page URL"
118
146
  )
119
147
 
120
- # Metadata
121
- description = models.TextField(
122
- blank=True,
123
- help_text="Payment description"
124
- )
125
- order_id = models.CharField(
126
- max_length=255,
148
+ # Transaction information
149
+ transaction_hash = models.CharField(
150
+ max_length=256,
127
151
  null=True,
128
152
  blank=True,
129
- help_text="Order reference ID"
130
- )
131
- metadata = models.JSONField(
132
- default=dict,
133
- help_text="Additional metadata"
153
+ db_index=True,
154
+ help_text="Blockchain transaction hash"
134
155
  )
135
156
 
136
- # Provider webhook data
137
- webhook_data = models.JSONField(
138
- null=True,
139
- blank=True,
140
- help_text="Raw webhook data from provider"
157
+ confirmations_count = models.PositiveIntegerField(
158
+ default=0,
159
+ help_text="Number of blockchain confirmations"
141
160
  )
142
161
 
143
- # Universal Security fields (used by all providers)
162
+ # Security and validation
144
163
  security_nonce = models.CharField(
145
164
  max_length=64,
146
165
  null=True,
147
166
  blank=True,
148
167
  db_index=True,
149
- help_text="Security nonce for replay attack protection (CryptAPI, Cryptomus, etc.)"
150
- )
151
- provider_callback_url = models.CharField(
152
- max_length=512,
153
- null=True,
154
- blank=True,
155
- help_text="Full callback URL with security parameters"
168
+ help_text="Security nonce for validation"
156
169
  )
157
170
 
158
- # Universal Transaction fields (crypto providers)
159
- transaction_hash = models.CharField(
160
- max_length=256,
161
- null=True,
162
- blank=True,
163
- db_index=True,
164
- help_text="Main transaction hash/ID (txid_in for CryptAPI, hash for Cryptomus)"
165
- )
166
- confirmation_hash = models.CharField(
167
- max_length=256,
171
+ # Timestamps
172
+ expires_at = models.DateTimeField(
168
173
  null=True,
169
174
  blank=True,
170
- help_text="Secondary transaction hash (txid_out for CryptAPI, confirmation for others)"
175
+ help_text="When this payment expires"
171
176
  )
172
- sender_address = models.CharField(
173
- max_length=200,
177
+
178
+ completed_at = models.DateTimeField(
174
179
  null=True,
175
180
  blank=True,
176
- help_text="Sender address (address_in for CryptAPI, from_address for Cryptomus)"
181
+ help_text="When this payment was completed"
177
182
  )
178
- receiver_address = models.CharField(
179
- max_length=200,
180
- null=True,
183
+
184
+ # Metadata and description
185
+ description = models.TextField(
181
186
  blank=True,
182
- help_text="Receiver address (address_out for CryptAPI, to_address for Cryptomus)"
187
+ help_text="Payment description"
183
188
  )
184
- crypto_amount = models.FloatField(
189
+
190
+ callback_url = models.URLField(
185
191
  null=True,
186
192
  blank=True,
187
- help_text="Amount in cryptocurrency units (value_coin for CryptAPI, amount for Cryptomus)"
188
- )
189
- confirmations_count = models.PositiveIntegerField(
190
- default=0,
191
- help_text="Number of blockchain confirmations"
193
+ help_text="Success callback URL"
192
194
  )
193
195
 
194
- # Timestamps
195
- expires_at = models.DateTimeField(
196
+ cancel_url = models.URLField(
196
197
  null=True,
197
198
  blank=True,
198
- help_text="Payment expiration time"
199
+ help_text="Cancellation URL"
199
200
  )
200
- completed_at = models.DateTimeField(
201
- null=True,
201
+
202
+ # Structured metadata (validated by Pydantic in services)
203
+ provider_data = models.JSONField(
204
+ default=dict,
202
205
  blank=True,
203
- help_text="Payment completion time"
206
+ help_text="Provider-specific data (validated by Pydantic)"
204
207
  )
205
- processed_at = models.DateTimeField(
206
- null=True,
208
+
209
+ webhook_data = models.JSONField(
210
+ default=dict,
207
211
  blank=True,
208
- help_text="When the payment was processed and funds added to balance"
212
+ help_text="Webhook data (validated by Pydantic)"
209
213
  )
210
-
211
- # Import and assign manager
212
- from ..managers import UniversalPaymentManager
213
- objects = UniversalPaymentManager()
214
-
214
+
215
+ # Manager
216
+ from .managers.payment_managers import PaymentManager
217
+ objects = PaymentManager()
218
+
215
219
  class Meta:
216
- db_table = 'universal_payments'
217
- verbose_name = "Universal Payment"
218
- verbose_name_plural = "Universal Payments"
220
+ db_table = 'payments_universal'
221
+ verbose_name = 'Universal Payment'
222
+ verbose_name_plural = 'Universal Payments'
223
+ ordering = ['-created_at']
219
224
  indexes = [
220
225
  models.Index(fields=['user', 'status']),
226
+ models.Index(fields=['provider', 'status']),
227
+ models.Index(fields=['status', 'created_at']),
221
228
  models.Index(fields=['provider_payment_id']),
222
- models.Index(fields=['internal_payment_id']),
223
- models.Index(fields=['status']),
224
- models.Index(fields=['provider']),
225
- models.Index(fields=['currency_code']),
226
- models.Index(fields=['created_at']),
227
- models.Index(fields=['processed_at']),
228
- # Universal crypto provider indexes
229
- models.Index(fields=['security_nonce']),
230
229
  models.Index(fields=['transaction_hash']),
231
- models.Index(fields=['confirmations_count']),
232
- models.Index(fields=['provider', 'status', 'confirmations_count']),
230
+ models.Index(fields=['expires_at']),
233
231
  ]
234
- ordering = ['-created_at']
235
-
232
+ constraints = [
233
+ models.CheckConstraint(
234
+ check=models.Q(amount_usd__gte=1.0),
235
+ name='payments_min_amount_check'
236
+ ),
237
+ models.CheckConstraint(
238
+ check=models.Q(amount_usd__lte=50000.0),
239
+ name='payments_max_amount_check'
240
+ ),
241
+ ]
242
+
236
243
  def __str__(self):
237
- return f"{self.user.email} - ${self.amount_usd} ({self.currency_code}) - {self.get_status_display()}"
238
-
244
+ return f"Payment {self.internal_payment_id} - ${self.amount_usd:.2f} {self.currency.code}"
245
+
246
+ def save(self, *args, **kwargs):
247
+ """Override save to generate internal payment ID."""
248
+ if not self.internal_payment_id:
249
+ # Generate internal payment ID
250
+ timestamp = timezone.now().strftime('%Y%m%d%H%M%S')
251
+ self.internal_payment_id = f"PAY_{timestamp}_{str(self.id)[:8]}"
252
+
253
+ super().save(*args, **kwargs)
254
+
255
+ def clean(self):
256
+ """Model validation."""
257
+ # Crypto payments must have network
258
+ if self.currency and self.currency.is_crypto and not self.network:
259
+ raise ValidationError("Cryptocurrency payments must specify a network")
260
+
261
+ # Fiat payments should not have network
262
+ if self.currency and self.currency.is_fiat and self.network:
263
+ raise ValidationError("Fiat payments should not specify a network")
264
+
265
+ # Validate amount limits
266
+ if self.amount_usd and (self.amount_usd < 1.0 or self.amount_usd > 50000.0):
267
+ raise ValidationError("Payment amount must be between $1.00 and $50,000.00")
268
+
269
+ # Validate expiration
270
+ if self.expires_at and self.expires_at <= timezone.now():
271
+ raise ValidationError("Expiration time must be in the future")
272
+
273
+ # Status properties
239
274
  @property
240
275
  def is_pending(self) -> bool:
241
- """Check if payment is still pending."""
242
- return self.status in [
243
- self.PaymentStatus.PENDING,
244
- self.PaymentStatus.CONFIRMING,
245
- self.PaymentStatus.CONFIRMED
246
- ]
247
-
276
+ """Check if payment is pending."""
277
+ return self.status == self.PaymentStatus.PENDING
278
+
248
279
  @property
249
280
  def is_completed(self) -> bool:
250
281
  """Check if payment is completed."""
251
282
  return self.status == self.PaymentStatus.COMPLETED
252
-
283
+
253
284
  @property
254
285
  def is_failed(self) -> bool:
255
286
  """Check if payment failed."""
256
- return self.status in [self.PaymentStatus.FAILED, self.PaymentStatus.EXPIRED]
257
-
258
- @property
259
- def needs_processing(self) -> bool:
260
- """Check if payment needs to be processed (completed but not processed)."""
261
- return self.is_completed and not self.processed_at
262
-
287
+ return self.status in [
288
+ self.PaymentStatus.FAILED,
289
+ self.PaymentStatus.EXPIRED,
290
+ self.PaymentStatus.CANCELLED
291
+ ]
292
+
263
293
  @property
264
- def is_crypto_payment(self) -> bool:
265
- """Check if this is a cryptocurrency payment."""
266
- return self.provider == self.PaymentProvider.NOWPAYMENTS
267
-
294
+ def is_expired(self) -> bool:
295
+ """Check if payment is expired."""
296
+ if not self.expires_at:
297
+ return False
298
+ return timezone.now() > self.expires_at
299
+
268
300
  @property
269
- def is_crypto_provider_payment(self) -> bool:
270
- """Check if this is a crypto provider payment (CryptAPI, Cryptomus, etc.)."""
271
- crypto_providers = ['cryptapi', 'cryptomus', 'nowpayments']
272
- return self.provider in crypto_providers or (self.security_nonce is not None)
273
-
301
+ def requires_confirmation(self) -> bool:
302
+ """Check if payment requires blockchain confirmation."""
303
+ return self.status in [
304
+ self.PaymentStatus.CONFIRMING,
305
+ self.PaymentStatus.CONFIRMED
306
+ ]
307
+
308
+ # Display properties
274
309
  @property
275
- def has_sufficient_confirmations(self) -> bool:
276
- """Check if payment has sufficient confirmations (3+ for most cryptos)."""
277
- required_confirmations = 3 # Can be made configurable per currency
278
- return self.confirmations_count >= required_confirmations
279
-
310
+ def status_color(self) -> str:
311
+ """Get color for status display."""
312
+ colors = {
313
+ self.PaymentStatus.PENDING: 'warning',
314
+ self.PaymentStatus.CONFIRMING: 'info',
315
+ self.PaymentStatus.CONFIRMED: 'primary',
316
+ self.PaymentStatus.COMPLETED: 'success',
317
+ self.PaymentStatus.FAILED: 'danger',
318
+ self.PaymentStatus.EXPIRED: 'secondary',
319
+ self.PaymentStatus.CANCELLED: 'secondary',
320
+ self.PaymentStatus.REFUNDED: 'warning',
321
+ }
322
+ return colors.get(self.status, 'secondary')
323
+
280
324
  @property
281
- def is_security_nonce_valid(self) -> bool:
282
- """Check if security nonce is present for crypto provider payments."""
283
- return bool(self.security_nonce) if self.is_crypto_provider_payment else True
284
-
325
+ def amount_display(self) -> str:
326
+ """Formatted amount display."""
327
+ return f"${self.amount_usd:.2f} USD"
328
+
285
329
  @property
286
- def has_transaction_hash(self) -> bool:
287
- """Check if payment has a transaction hash."""
288
- return bool(self.transaction_hash)
289
-
290
- def get_payment_url(self) -> str:
291
- """Get payment URL for QR code or direct payment."""
292
- if self.pay_address and self.pay_amount:
293
- return f"{self.currency_code.lower()}:{self.pay_address}?amount={self.pay_amount}"
294
- return ""
295
-
296
- def get_qr_code_url(self, size: int = 200) -> str:
297
- """Get QR code URL for payment."""
298
- payment_url = self.get_payment_url()
299
- if payment_url:
300
- return f"https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&data={payment_url}"
301
- return ""
302
-
303
- def mark_as_processed(self):
304
- """Mark payment as processed."""
305
- if not self.processed_at:
306
- self.processed_at = timezone.now()
307
- self.save(update_fields=['processed_at'])
308
-
309
- def update_from_webhook(self, webhook_data: dict):
310
- """Update payment from provider webhook data."""
311
- self.webhook_data = webhook_data
312
-
313
- # Update status if provided
314
- if 'payment_status' in webhook_data:
315
- self.status = webhook_data['payment_status']
316
-
317
- # Update payment details if provided
318
- if 'pay_address' in webhook_data:
319
- self.pay_address = webhook_data['pay_address']
320
-
321
- if 'pay_amount' in webhook_data:
322
- self.pay_amount = float(str(webhook_data['pay_amount']))
323
-
324
- if 'payment_id' in webhook_data:
325
- self.provider_payment_id = webhook_data['payment_id']
326
-
327
- # Universal crypto provider webhook fields
328
- # CryptAPI format
329
- if 'txid_in' in webhook_data:
330
- self.transaction_hash = webhook_data['txid_in']
331
- if 'txid_out' in webhook_data:
332
- self.confirmation_hash = webhook_data['txid_out']
333
- if 'address_in' in webhook_data:
334
- self.sender_address = webhook_data['address_in']
335
- if 'address_out' in webhook_data:
336
- self.receiver_address = webhook_data['address_out']
337
- if 'value_coin' in webhook_data:
338
- self.crypto_amount = float(str(webhook_data['value_coin']))
339
-
340
- # Cryptomus format
341
- if 'hash' in webhook_data:
342
- self.transaction_hash = webhook_data['hash']
343
- if 'from_address' in webhook_data:
344
- self.sender_address = webhook_data['from_address']
345
- if 'to_address' in webhook_data:
346
- self.receiver_address = webhook_data['to_address']
347
- if 'amount' in webhook_data and isinstance(webhook_data['amount'], (int, float, str)):
348
- try:
349
- self.crypto_amount = float(str(webhook_data['amount']))
350
- except (ValueError, TypeError):
351
- pass
352
-
353
- # Universal confirmations field
354
- if 'confirmations' in webhook_data:
355
- self.confirmations_count = int(webhook_data['confirmations'])
356
-
357
- self.save()
358
-
330
+ def crypto_amount_display(self) -> str:
331
+ """Formatted crypto amount display."""
332
+ if not self.pay_amount:
333
+ return "N/A"
334
+ return f"{self.pay_amount:.8f} {self.currency.code}"
335
+
336
+ # Business logic methods
337
+ def can_be_cancelled(self) -> bool:
338
+ """Check if payment can be cancelled."""
339
+ return self.status in [
340
+ self.PaymentStatus.PENDING,
341
+ self.PaymentStatus.CONFIRMING
342
+ ]
343
+
359
344
  def can_be_refunded(self) -> bool:
360
345
  """Check if payment can be refunded."""
361
- return self.is_completed and self.processed_at
362
-
363
- def get_currency_display_name(self) -> str:
364
- """Get human-readable currency name."""
365
- # This could be enhanced to lookup from Currency model
366
- currency_names = {
367
- 'BTC': 'Bitcoin',
368
- 'ETH': 'Ethereum',
369
- 'USD': 'US Dollar',
370
- 'EUR': 'Euro',
371
- }
372
- return currency_names.get(self.currency_code, self.currency_code)
373
-
374
- def get_status_color(self) -> str:
375
- """Get color for status display."""
376
- status_colors = {
377
- self.PaymentStatus.PENDING: '#6c757d',
378
- self.PaymentStatus.CONFIRMING: '#fd7e14',
379
- self.PaymentStatus.CONFIRMED: '#20c997',
380
- self.PaymentStatus.COMPLETED: '#198754',
381
- self.PaymentStatus.FAILED: '#dc3545',
382
- self.PaymentStatus.REFUNDED: '#6f42c1',
383
- self.PaymentStatus.EXPIRED: '#dc3545',
384
- self.PaymentStatus.CANCELLED: '#6c757d'
385
- }
386
- return status_colors.get(self.status, '#6c757d')
387
-
388
- def clean(self):
389
- """Validate payment data."""
390
-
391
- # Validate minimum amount
392
- if self.amount_usd < 1.0:
393
- raise ValidationError("Minimum payment amount is $1.00")
394
-
395
- # Validate crypto address for crypto payments
396
- if self.is_crypto_payment and self.status != self.PaymentStatus.PENDING:
397
- if not self.pay_address:
398
- raise ValidationError("Payment address is required for crypto payments")
399
-
400
- def save(self, *args, **kwargs):
401
- """Override save to run validation."""
402
- if self.currency_code:
403
- self.currency_code = self.currency_code.upper()
404
-
405
- # Generate internal payment ID if not set
406
- if not self.internal_payment_id:
407
- import uuid
408
- self.internal_payment_id = f"pay_{str(uuid.uuid4())[:8]}"
409
-
410
- self.clean()
411
- super().save(*args, **kwargs)
346
+ return self.status == self.PaymentStatus.COMPLETED
347
+
348
+ def mark_completed(self, actual_amount_usd: float = None, transaction_hash: str = None):
349
+ """Mark payment as completed (delegates to manager)."""
350
+ return self.__class__.objects.mark_payment_completed(
351
+ self, actual_amount_usd, transaction_hash
352
+ )
353
+
354
+ def mark_failed(self, reason: str = None, error_code: str = None):
355
+ """Mark payment as failed (delegates to manager)."""
356
+ return self.__class__.objects.mark_payment_failed(
357
+ self, reason, error_code
358
+ )
359
+
360
+ def cancel(self, reason: str = None):
361
+ """Cancel payment (delegates to manager)."""
362
+ return self.__class__.objects.cancel_payment(self, reason)