django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +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
-
17
19
  class UniversalPayment(UUIDTimestampedModel):
18
- """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
+ """
19
26
 
20
27
  class PaymentStatus(models.TextChoices):
21
28
  PENDING = "pending", "Pending"
@@ -29,384 +36,327 @@ class UniversalPayment(UUIDTimestampedModel):
29
36
 
30
37
  class PaymentProvider(models.TextChoices):
31
38
  NOWPAYMENTS = "nowpayments", "NowPayments"
32
- CRYPTAPI = "cryptapi", "CryptAPI"
33
- CRYPTOMUS = "cryptomus", "Cryptomus"
34
- STRIPE = "stripe", "Stripe"
35
- 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()
36
50
 
51
+ # User and identification
37
52
  user = models.ForeignKey(
38
53
  User,
39
54
  on_delete=models.CASCADE,
40
- related_name='universal_payments',
41
- help_text="User who initiated this payment"
55
+ related_name='payments',
56
+ help_text="User who created this payment"
42
57
  )
43
58
 
44
- # Financial data
59
+ internal_payment_id = models.CharField(
60
+ max_length=100,
61
+ unique=True,
62
+ db_index=True,
63
+ help_text="Internal payment identifier"
64
+ )
65
+
66
+ # Financial information (USD as float per requirements)
45
67
  amount_usd = models.FloatField(
46
- validators=[MinValueValidator(1.0)],
47
- help_text="Payment amount in USD"
68
+ validators=[MinValueValidator(1.0), MaxValueValidator(50000.0)],
69
+ help_text="Payment amount in USD (float for performance)"
48
70
  )
49
- currency_code = models.CharField(
50
- max_length=10,
51
- 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"
52
78
  )
53
79
 
54
- # Actual received amount (may differ from requested)
55
- actual_amount_usd = models.FloatField(
80
+ network = models.ForeignKey(
81
+ Network,
82
+ on_delete=models.PROTECT,
83
+ related_name='payments',
56
84
  null=True,
57
85
  blank=True,
58
- help_text="Actual received amount in USD"
86
+ help_text="Blockchain network (for crypto payments)"
59
87
  )
60
- actual_currency_code = models.CharField(
61
- max_length=10,
88
+
89
+ # Crypto amounts use Decimal for precision
90
+ pay_amount = models.DecimalField(
91
+ max_digits=20,
92
+ decimal_places=8,
62
93
  null=True,
63
94
  blank=True,
64
- 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"
65
102
  )
66
103
 
67
- # Fee information
68
104
  fee_amount_usd = models.FloatField(
69
105
  null=True,
70
106
  blank=True,
71
- validators=[MinValueValidator(0.0)],
72
107
  help_text="Fee amount in USD"
73
108
  )
74
109
 
75
- # Payment details
110
+ # Provider information
76
111
  provider = models.CharField(
77
112
  max_length=50,
78
113
  choices=PaymentProvider.choices,
114
+ default=PaymentProvider.NOWPAYMENTS,
79
115
  help_text="Payment provider"
80
116
  )
81
- status = models.CharField(
82
- max_length=20,
83
- choices=PaymentStatus.choices,
84
- default=PaymentStatus.PENDING,
85
- help_text="Payment status"
86
- )
87
117
 
88
- # Provider-specific fields
89
118
  provider_payment_id = models.CharField(
90
119
  max_length=255,
91
120
  null=True,
92
121
  blank=True,
93
- unique=True,
122
+ db_index=True,
94
123
  help_text="Provider's payment ID"
95
124
  )
96
- internal_payment_id = models.CharField(
97
- max_length=100,
98
- unique=True,
99
- 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"
100
133
  )
101
134
 
102
- # Crypto payment specific
103
135
  pay_address = models.CharField(
104
- max_length=200,
136
+ max_length=255,
105
137
  null=True,
106
138
  blank=True,
107
139
  help_text="Cryptocurrency payment address"
108
140
  )
109
- pay_amount = models.FloatField(
110
- null=True,
111
- blank=True,
112
- help_text="Amount to pay in cryptocurrency"
113
- )
114
- network = models.CharField(
115
- max_length=50,
141
+
142
+ payment_url = models.URLField(
116
143
  null=True,
117
144
  blank=True,
118
- help_text="Blockchain network (mainnet, testnet, etc.)"
145
+ help_text="Payment page URL"
119
146
  )
120
147
 
121
- # Metadata
122
- description = models.TextField(
123
- blank=True,
124
- help_text="Payment description"
125
- )
126
- order_id = models.CharField(
127
- max_length=255,
148
+ # Transaction information
149
+ transaction_hash = models.CharField(
150
+ max_length=256,
128
151
  null=True,
129
152
  blank=True,
130
- help_text="Order reference ID"
131
- )
132
- metadata = models.JSONField(
133
- default=dict,
134
- help_text="Additional metadata"
153
+ db_index=True,
154
+ help_text="Blockchain transaction hash"
135
155
  )
136
156
 
137
- # Provider webhook data
138
- webhook_data = models.JSONField(
139
- null=True,
140
- blank=True,
141
- help_text="Raw webhook data from provider"
157
+ confirmations_count = models.PositiveIntegerField(
158
+ default=0,
159
+ help_text="Number of blockchain confirmations"
142
160
  )
143
161
 
144
- # Universal Security fields (used by all providers)
162
+ # Security and validation
145
163
  security_nonce = models.CharField(
146
164
  max_length=64,
147
165
  null=True,
148
166
  blank=True,
149
167
  db_index=True,
150
- help_text="Security nonce for replay attack protection (CryptAPI, Cryptomus, etc.)"
151
- )
152
- provider_callback_url = models.CharField(
153
- max_length=512,
154
- null=True,
155
- blank=True,
156
- help_text="Full callback URL with security parameters"
168
+ help_text="Security nonce for validation"
157
169
  )
158
170
 
159
- # Universal Transaction fields (crypto providers)
160
- transaction_hash = models.CharField(
161
- max_length=256,
162
- null=True,
163
- blank=True,
164
- db_index=True,
165
- help_text="Main transaction hash/ID (txid_in for CryptAPI, hash for Cryptomus)"
166
- )
167
- confirmation_hash = models.CharField(
168
- max_length=256,
171
+ # Timestamps
172
+ expires_at = models.DateTimeField(
169
173
  null=True,
170
174
  blank=True,
171
- help_text="Secondary transaction hash (txid_out for CryptAPI, confirmation for others)"
175
+ help_text="When this payment expires"
172
176
  )
173
- sender_address = models.CharField(
174
- max_length=200,
177
+
178
+ completed_at = models.DateTimeField(
175
179
  null=True,
176
180
  blank=True,
177
- help_text="Sender address (address_in for CryptAPI, from_address for Cryptomus)"
181
+ help_text="When this payment was completed"
178
182
  )
179
- receiver_address = models.CharField(
180
- max_length=200,
181
- null=True,
183
+
184
+ # Metadata and description
185
+ description = models.TextField(
182
186
  blank=True,
183
- help_text="Receiver address (address_out for CryptAPI, to_address for Cryptomus)"
187
+ help_text="Payment description"
184
188
  )
185
- crypto_amount = models.FloatField(
189
+
190
+ callback_url = models.URLField(
186
191
  null=True,
187
192
  blank=True,
188
- help_text="Amount in cryptocurrency units (value_coin for CryptAPI, amount for Cryptomus)"
189
- )
190
- confirmations_count = models.PositiveIntegerField(
191
- default=0,
192
- help_text="Number of blockchain confirmations"
193
+ help_text="Success callback URL"
193
194
  )
194
195
 
195
- # Timestamps
196
- expires_at = models.DateTimeField(
196
+ cancel_url = models.URLField(
197
197
  null=True,
198
198
  blank=True,
199
- help_text="Payment expiration time"
199
+ help_text="Cancellation URL"
200
200
  )
201
- completed_at = models.DateTimeField(
202
- null=True,
201
+
202
+ # Structured metadata (validated by Pydantic in services)
203
+ provider_data = models.JSONField(
204
+ default=dict,
203
205
  blank=True,
204
- help_text="Payment completion time"
206
+ help_text="Provider-specific data (validated by Pydantic)"
205
207
  )
206
- processed_at = models.DateTimeField(
207
- null=True,
208
+
209
+ webhook_data = models.JSONField(
210
+ default=dict,
208
211
  blank=True,
209
- help_text="When the payment was processed and funds added to balance"
212
+ help_text="Webhook data (validated by Pydantic)"
210
213
  )
211
-
212
- # Custom managers for optimized queries
213
- from ..managers.payment_manager import UniversalPaymentManager
214
- objects = UniversalPaymentManager()
215
-
214
+
215
+ # Manager
216
+ from .managers.payment_managers import PaymentManager
217
+ objects = PaymentManager()
218
+
216
219
  class Meta:
217
- db_table = 'universal_payments'
218
- verbose_name = "Universal Payment"
219
- 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']
220
224
  indexes = [
221
225
  models.Index(fields=['user', 'status']),
226
+ models.Index(fields=['provider', 'status']),
227
+ models.Index(fields=['status', 'created_at']),
222
228
  models.Index(fields=['provider_payment_id']),
223
- models.Index(fields=['internal_payment_id']),
224
- models.Index(fields=['status']),
225
- models.Index(fields=['provider']),
226
- models.Index(fields=['currency_code']),
227
- models.Index(fields=['created_at']),
228
- models.Index(fields=['processed_at']),
229
- # Universal crypto provider indexes
230
- models.Index(fields=['security_nonce']),
231
229
  models.Index(fields=['transaction_hash']),
232
- models.Index(fields=['confirmations_count']),
233
- models.Index(fields=['provider', 'status', 'confirmations_count']),
230
+ models.Index(fields=['expires_at']),
234
231
  ]
235
- ordering = ['-created_at']
236
-
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
+
237
243
  def __str__(self):
238
- return f"{self.user.email} - ${self.amount_usd} ({self.currency_code}) - {self.get_status_display()}"
239
-
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
240
274
  @property
241
275
  def is_pending(self) -> bool:
242
- """Check if payment is still pending."""
243
- return self.status in [
244
- self.PaymentStatus.PENDING,
245
- self.PaymentStatus.CONFIRMING,
246
- self.PaymentStatus.CONFIRMED
247
- ]
248
-
276
+ """Check if payment is pending."""
277
+ return self.status == self.PaymentStatus.PENDING
278
+
249
279
  @property
250
280
  def is_completed(self) -> bool:
251
281
  """Check if payment is completed."""
252
282
  return self.status == self.PaymentStatus.COMPLETED
253
-
283
+
254
284
  @property
255
285
  def is_failed(self) -> bool:
256
286
  """Check if payment failed."""
257
- return self.status in [self.PaymentStatus.FAILED, self.PaymentStatus.EXPIRED]
258
-
259
- @property
260
- def needs_processing(self) -> bool:
261
- """Check if payment needs to be processed (completed but not processed)."""
262
- return self.is_completed and not self.processed_at
263
-
287
+ return self.status in [
288
+ self.PaymentStatus.FAILED,
289
+ self.PaymentStatus.EXPIRED,
290
+ self.PaymentStatus.CANCELLED
291
+ ]
292
+
264
293
  @property
265
- def is_crypto_payment(self) -> bool:
266
- """Check if this is a cryptocurrency payment."""
267
- return self.provider == self.PaymentProvider.NOWPAYMENTS
268
-
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
+
269
300
  @property
270
- def is_crypto_provider_payment(self) -> bool:
271
- """Check if this is a crypto provider payment (CryptAPI, Cryptomus, etc.)."""
272
- crypto_providers = ['cryptapi', 'cryptomus', 'nowpayments']
273
- return self.provider in crypto_providers or (self.security_nonce is not None)
274
-
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
275
309
  @property
276
- def has_sufficient_confirmations(self) -> bool:
277
- """Check if payment has sufficient confirmations (3+ for most cryptos)."""
278
- required_confirmations = 3 # Can be made configurable per currency
279
- return self.confirmations_count >= required_confirmations
280
-
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
+
281
324
  @property
282
- def is_security_nonce_valid(self) -> bool:
283
- """Check if security nonce is present for crypto provider payments."""
284
- return bool(self.security_nonce) if self.is_crypto_provider_payment else True
285
-
325
+ def amount_display(self) -> str:
326
+ """Formatted amount display."""
327
+ return f"${self.amount_usd:.2f} USD"
328
+
286
329
  @property
287
- def has_transaction_hash(self) -> bool:
288
- """Check if payment has a transaction hash."""
289
- return bool(self.transaction_hash)
290
-
291
- def get_payment_url(self) -> str:
292
- """Get payment URL for QR code or direct payment."""
293
- if self.pay_address and self.pay_amount:
294
- return f"{self.currency_code.lower()}:{self.pay_address}?amount={self.pay_amount}"
295
- return ""
296
-
297
- def get_qr_code_url(self, size: int = 200) -> str:
298
- """Get QR code URL for payment."""
299
- payment_url = self.get_payment_url()
300
- if payment_url:
301
- return f"https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&data={payment_url}"
302
- return ""
303
-
304
- def mark_as_processed(self):
305
- """Mark payment as processed."""
306
- if not self.processed_at:
307
- self.processed_at = timezone.now()
308
- self.save(update_fields=['processed_at'])
309
-
310
- def update_from_webhook(self, webhook_data: dict):
311
- """Update payment from provider webhook data."""
312
- self.webhook_data = webhook_data
313
-
314
- # Update status if provided
315
- if 'payment_status' in webhook_data:
316
- self.status = webhook_data['payment_status']
317
-
318
- # Update payment details if provided
319
- if 'pay_address' in webhook_data:
320
- self.pay_address = webhook_data['pay_address']
321
-
322
- if 'pay_amount' in webhook_data:
323
- self.pay_amount = float(str(webhook_data['pay_amount']))
324
-
325
- if 'payment_id' in webhook_data:
326
- self.provider_payment_id = webhook_data['payment_id']
327
-
328
- # Universal crypto provider webhook fields
329
- # CryptAPI format
330
- if 'txid_in' in webhook_data:
331
- self.transaction_hash = webhook_data['txid_in']
332
- if 'txid_out' in webhook_data:
333
- self.confirmation_hash = webhook_data['txid_out']
334
- if 'address_in' in webhook_data:
335
- self.sender_address = webhook_data['address_in']
336
- if 'address_out' in webhook_data:
337
- self.receiver_address = webhook_data['address_out']
338
- if 'value_coin' in webhook_data:
339
- self.crypto_amount = float(str(webhook_data['value_coin']))
340
-
341
- # Cryptomus format
342
- if 'hash' in webhook_data:
343
- self.transaction_hash = webhook_data['hash']
344
- if 'from_address' in webhook_data:
345
- self.sender_address = webhook_data['from_address']
346
- if 'to_address' in webhook_data:
347
- self.receiver_address = webhook_data['to_address']
348
- if 'amount' in webhook_data and isinstance(webhook_data['amount'], (int, float, str)):
349
- try:
350
- self.crypto_amount = float(str(webhook_data['amount']))
351
- except (ValueError, TypeError):
352
- pass
353
-
354
- # Universal confirmations field
355
- if 'confirmations' in webhook_data:
356
- self.confirmations_count = int(webhook_data['confirmations'])
357
-
358
- self.save()
359
-
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
+
360
344
  def can_be_refunded(self) -> bool:
361
345
  """Check if payment can be refunded."""
362
- return self.is_completed and self.processed_at
363
-
364
- def get_currency_display_name(self) -> str:
365
- """Get human-readable currency name."""
366
- # This could be enhanced to lookup from Currency model
367
- currency_names = {
368
- 'BTC': 'Bitcoin',
369
- 'ETH': 'Ethereum',
370
- 'USD': 'US Dollar',
371
- 'EUR': 'Euro',
372
- }
373
- return currency_names.get(self.currency_code, self.currency_code)
374
-
375
- def get_status_color(self) -> str:
376
- """Get color for status display."""
377
- status_colors = {
378
- self.PaymentStatus.PENDING: '#6c757d',
379
- self.PaymentStatus.CONFIRMING: '#fd7e14',
380
- self.PaymentStatus.CONFIRMED: '#20c997',
381
- self.PaymentStatus.COMPLETED: '#198754',
382
- self.PaymentStatus.FAILED: '#dc3545',
383
- self.PaymentStatus.REFUNDED: '#6f42c1',
384
- self.PaymentStatus.EXPIRED: '#dc3545',
385
- self.PaymentStatus.CANCELLED: '#6c757d'
386
- }
387
- return status_colors.get(self.status, '#6c757d')
388
-
389
- def clean(self):
390
- """Validate payment data."""
391
-
392
- # Validate minimum amount
393
- if self.amount_usd < 1.0:
394
- raise ValidationError("Minimum payment amount is $1.00")
395
-
396
- # Validate crypto address for crypto payments
397
- if self.is_crypto_payment and self.status != self.PaymentStatus.PENDING:
398
- if not self.pay_address:
399
- raise ValidationError("Payment address is required for crypto payments")
400
-
401
- def save(self, *args, **kwargs):
402
- """Override save to run validation."""
403
- if self.currency_code:
404
- self.currency_code = self.currency_code.upper()
405
-
406
- # Generate internal payment ID if not set
407
- if not self.internal_payment_id:
408
- import uuid
409
- self.internal_payment_id = f"pay_{str(uuid.uuid4())[:8]}"
410
-
411
- self.clean()
412
- 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)