velocity-python 0.0.232__tar.gz → 0.0.234__tar.gz

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 (161) hide show
  1. {velocity_python-0.0.232/src/velocity_python.egg-info → velocity_python-0.0.234}/PKG-INFO +1 -1
  2. {velocity_python-0.0.232 → velocity_python-0.0.234}/pyproject.toml +1 -1
  3. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/__init__.py +8 -0
  5. velocity_python-0.0.234/src/velocity/payment/charge_rules.py +39 -0
  6. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/demo_profiles.py +78 -29
  7. {velocity_python-0.0.232 → velocity_python-0.0.234/src/velocity_python.egg-info}/PKG-INFO +1 -1
  8. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  9. {velocity_python-0.0.232 → velocity_python-0.0.234}/LICENSE +0 -0
  10. {velocity_python-0.0.232 → velocity_python-0.0.234}/README.md +0 -0
  11. {velocity_python-0.0.232 → velocity_python-0.0.234}/setup.cfg +0 -0
  12. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/__init__.py +0 -0
  13. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/invoices.py +0 -0
  14. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/orders.py +0 -0
  15. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/payments.py +0 -0
  16. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/purchase_orders.py +0 -0
  17. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/tests/__init__.py +0 -0
  18. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/tests/test_email_processing.py +0 -0
  19. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
  20. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
  21. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/__init__.py +0 -0
  22. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/amplify.py +0 -0
  23. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/amplify_build.py +0 -0
  24. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/__init__.py +0 -0
  25. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/base_handler.py +0 -0
  26. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/context.py +0 -0
  27. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/context_factory.py +0 -0
  28. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/exceptions.py +0 -0
  29. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  30. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  31. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  32. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  33. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/perf.py +0 -0
  34. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/response.py +0 -0
  35. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  36. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/tests/__init__.py +0 -0
  37. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  38. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  39. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/aws/tests/test_response.py +0 -0
  40. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/__init__.py +0 -0
  41. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/__init__.py +0 -0
  42. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/column.py +0 -0
  43. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/database.py +0 -0
  44. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/decorators.py +0 -0
  45. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/engine.py +0 -0
  46. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/result.py +0 -0
  47. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/row.py +0 -0
  48. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/sequence.py +0 -0
  49. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/table.py +0 -0
  50. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/transaction.py +0 -0
  51. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/core/view.py +0 -0
  52. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/exceptions.py +0 -0
  53. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/__init__.py +0 -0
  54. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/base/__init__.py +0 -0
  55. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/base/initializer.py +0 -0
  56. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/base/operators.py +0 -0
  57. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/base/sql.py +0 -0
  58. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/base/types.py +0 -0
  59. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/mysql/__init__.py +0 -0
  60. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/mysql/operators.py +0 -0
  61. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/mysql/reserved.py +0 -0
  62. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/mysql/sql.py +0 -0
  63. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/mysql/types.py +0 -0
  64. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/postgres/__init__.py +0 -0
  65. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/postgres/operators.py +0 -0
  66. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/postgres/reserved.py +0 -0
  67. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/postgres/sql.py +0 -0
  68. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/postgres/types.py +0 -0
  69. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  70. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlite/operators.py +0 -0
  71. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  72. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlite/sql.py +0 -0
  73. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlite/types.py +0 -0
  74. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  75. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  76. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  77. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  78. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/sqlserver/types.py +0 -0
  79. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/servers/tablehelper.py +0 -0
  80. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/__init__.py +0 -0
  81. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/common_db_test.py +0 -0
  82. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/__init__.py +0 -0
  83. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/common.py +0 -0
  84. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_column.py +0 -0
  85. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  86. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_database.py +0 -0
  87. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  88. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  89. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  90. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_result.py +0 -0
  91. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_row.py +0 -0
  92. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  93. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  94. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  95. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  96. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  97. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_table.py +0 -0
  98. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  99. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  100. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/sql/__init__.py +0 -0
  101. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/sql/common.py +0 -0
  102. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  103. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  104. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  105. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_db_utils.py +0 -0
  106. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_postgres.py +0 -0
  107. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  108. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  109. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_result_caching.py +0 -0
  110. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  111. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  112. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  113. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  114. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_sql_builder.py +0 -0
  115. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_tablehelper.py +0 -0
  116. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/tests/test_view_helper.py +0 -0
  117. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/db/utils.py +0 -0
  118. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/logging.py +0 -0
  119. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/__init__.py +0 -0
  120. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/conv/__init__.py +0 -0
  121. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/conv/iconv.py +0 -0
  122. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/conv/oconv.py +0 -0
  123. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/db.py +0 -0
  124. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/export.py +0 -0
  125. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/format.py +0 -0
  126. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/mail.py +0 -0
  127. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/merge.py +0 -0
  128. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/__init__.py +0 -0
  129. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_db.py +0 -0
  130. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_fix.py +0 -0
  131. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_format.py +0 -0
  132. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_iconv.py +0 -0
  133. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_merge.py +0 -0
  134. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_oconv.py +0 -0
  135. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_original_error.py +0 -0
  136. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tests/test_timer.py +0 -0
  137. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/timer.py +0 -0
  138. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/misc/tools.py +0 -0
  139. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/authorizenet_adapter.py +0 -0
  140. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/base_adapter.py +0 -0
  141. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/braintree_adapter.py +0 -0
  142. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/profiles.py +0 -0
  143. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/router.py +0 -0
  144. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity/payment/stripe_adapter.py +0 -0
  145. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  146. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity_python.egg-info/requires.txt +0 -0
  147. {velocity_python-0.0.232 → velocity_python-0.0.234}/src/velocity_python.egg-info/top_level.txt +0 -0
  148. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_amplify_build.py +0 -0
  149. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_decorators.py +0 -0
  150. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_iconv_money_to_cents.py +0 -0
  151. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_lambda_handler.py +0 -0
  152. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_lambda_handler_auth.py +0 -0
  153. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_mixins_import.py +0 -0
  154. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_payment_braintree_adapter.py +0 -0
  155. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_payment_demo_profiles.py +0 -0
  156. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_payment_profiles.py +0 -0
  157. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_payment_router.py +0 -0
  158. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_payment_stripe_adapter.py +0 -0
  159. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  160. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_table_alter.py +0 -0
  161. {velocity_python-0.0.232 → velocity_python-0.0.234}/tests/test_where_clause_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.232
3
+ Version: 0.0.234
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.0.232"
7
+ version = "0.0.234"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.232"
1
+ __version__ = version = "0.0.234"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -55,6 +55,11 @@ from .profiles import (
55
55
  upsert_payment_profile,
56
56
  )
57
57
  from .demo_profiles import resolve_demo_charge_cards, get_card_lookup_id
58
+ from .charge_rules import (
59
+ should_charge_immediately,
60
+ is_below_immediate_charge_minimum,
61
+ is_above_immediate_charge_maximum,
62
+ )
58
63
 
59
64
  __all__ = [
60
65
  # Base classes
@@ -83,6 +88,9 @@ __all__ = [
83
88
  "upsert_payment_profile",
84
89
  "resolve_demo_charge_cards",
85
90
  "get_card_lookup_id",
91
+ "should_charge_immediately",
92
+ "is_below_immediate_charge_minimum",
93
+ "is_above_immediate_charge_maximum",
86
94
  ]
87
95
 
88
96
  __version__ = "1.0.0"
@@ -0,0 +1,39 @@
1
+ """Shared immediate-charge validation helpers for payment flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def should_charge_immediately(total_charge_amount: Any) -> bool:
9
+ """Return True when an immediate payment should be submitted to a processor."""
10
+ if total_charge_amount in (None, ""):
11
+ return False
12
+ try:
13
+ return float(total_charge_amount) > 0
14
+ except (TypeError, ValueError):
15
+ return False
16
+
17
+
18
+ def is_below_immediate_charge_minimum(
19
+ total_charge_amount: Any, minimum_donation_amount: Any
20
+ ) -> bool:
21
+ """Return True only when an actual immediate charge exists and is below the minimum."""
22
+ if not should_charge_immediately(total_charge_amount):
23
+ return False
24
+ try:
25
+ return float(total_charge_amount) < float(minimum_donation_amount)
26
+ except (TypeError, ValueError):
27
+ return False
28
+
29
+
30
+ def is_above_immediate_charge_maximum(
31
+ total_charge_amount: Any, maximum_donation_amount: Any
32
+ ) -> bool:
33
+ """Return True only when an actual immediate charge exists and is above the maximum."""
34
+ if not should_charge_immediately(total_charge_amount):
35
+ return False
36
+ try:
37
+ return float(total_charge_amount) > float(maximum_donation_amount)
38
+ except (TypeError, ValueError):
39
+ return False
@@ -1,16 +1,23 @@
1
1
  """Demo-only payment profile helpers for charging copied production users safely."""
2
2
 
3
+ import hashlib
3
4
  import os
4
5
  from typing import Dict, List, Optional
5
6
 
6
7
  from .profiles import get_payment_profile_sources, upsert_payment_profile
7
8
  from .router import get_processor_config
8
9
 
9
- _DEMO_BRAINTREE_NONCE = "fake-valid-nonce"
10
10
  _DEMO_STRIPE_TOKEN = "tok_visa"
11
11
  _DEMO_AUTHORIZE_CARD_NUMBER = "4111111111111111"
12
12
  _DEMO_AUTHORIZE_EXPIRATION = "2030-12"
13
13
  _DEMO_AUTHORIZE_CARD_CODE = "123"
14
+ _DEMO_BRAINTREE_CARD_NUMBERS = [
15
+ "4111111111111111",
16
+ "4012888888881881",
17
+ "5555555555554444",
18
+ "6011000991300009",
19
+ "378282246310005",
20
+ ]
14
21
 
15
22
 
16
23
  def is_demo_environment() -> bool:
@@ -39,9 +46,42 @@ def _save_payment_profile(tx, data: Dict) -> Dict:
39
46
  return data
40
47
 
41
48
 
49
+ def _stable_hash(value: str) -> int:
50
+ return int(hashlib.sha256(str(value).encode("utf-8")).hexdigest(), 16)
51
+
52
+
53
+ def _build_demo_braintree_card_spec(email_address: str) -> Dict[str, str]:
54
+ digest = _stable_hash(email_address)
55
+ card_number = _DEMO_BRAINTREE_CARD_NUMBERS[digest % len(_DEMO_BRAINTREE_CARD_NUMBERS)]
56
+ expiration_month = (digest % 12) + 1
57
+ expiration_year = 2027 + ((digest // len(_DEMO_BRAINTREE_CARD_NUMBERS)) % 3)
58
+ postal_code = f"{10000 + (digest % 89999):05d}"
59
+ return {
60
+ "number": card_number,
61
+ "expiration_date": f"{expiration_month:02d}/{expiration_year}",
62
+ "last4": card_number[-4:],
63
+ "expiration_month": f"{expiration_month:02d}",
64
+ "expiration_year": str(expiration_year),
65
+ "postal_code": postal_code,
66
+ }
67
+
68
+
69
+ def _find_matching_braintree_payment_method(customer, card_spec: Dict[str, str]):
70
+ for payment_method in customer.payment_methods:
71
+ if getattr(payment_method, "last_4", None) != card_spec["last4"]:
72
+ continue
73
+ if str(getattr(payment_method, "expiration_month", "")).zfill(2) != card_spec["expiration_month"]:
74
+ continue
75
+ if str(getattr(payment_method, "expiration_year", "")) != card_spec["expiration_year"]:
76
+ continue
77
+ return payment_method
78
+ return None
79
+
80
+
42
81
  def get_card_lookup_id(card: Dict) -> str:
43
82
  return str(
44
- card.get("sys_id")
83
+ card.get("lookup_id")
84
+ or card.get("sys_id")
45
85
  or card.get("payment_profile_id")
46
86
  or card.get("customer_profile_id")
47
87
  or card.get("src")
@@ -68,31 +108,35 @@ def _get_demo_braintree_card(tx, user: Dict) -> Dict:
68
108
  )
69
109
 
70
110
  demo_email = build_demo_profile_email(user["email_address"], "bt")
111
+ card_spec = _build_demo_braintree_card_spec(user["email_address"])
71
112
  customer = None
72
113
 
73
114
  collection = gateway.customer.search(braintree.CustomerSearch.email == demo_email)
74
115
  for existing_customer in collection.items:
75
- customer = existing_customer
76
- for payment_method in existing_customer.payment_methods:
77
- return _save_payment_profile(
78
- tx,
79
- {
80
- "src": "BT",
81
- "card_type": getattr(payment_method, "card_type", "Visa"),
82
- "card_number": getattr(payment_method, "last_4", "1111"),
83
- "expiration_date": _format_expiration_date(
84
- getattr(payment_method, "expiration_year", "2030"),
85
- getattr(payment_method, "expiration_month", "12"),
86
- ),
87
- "payment_profile_id": payment_method.token,
88
- "customer_profile_id": payment_method.customer_id,
89
- "email_address": user["email_address"],
90
- "first_name": user.get("first_name")
91
- or user["email_address"].split("@")[0],
92
- "last_name": user.get("last_name") or "Demo",
93
- "is_default": getattr(payment_method, "default", True),
94
- },
95
- )
116
+ customer = existing_customer
117
+ payment_method = _find_matching_braintree_payment_method(
118
+ existing_customer, card_spec
119
+ )
120
+ if payment_method:
121
+ return _save_payment_profile(
122
+ tx,
123
+ {
124
+ "src": "BT",
125
+ "card_type": getattr(payment_method, "card_type", "Visa"),
126
+ "card_number": getattr(payment_method, "last_4", card_spec["last4"]),
127
+ "expiration_date": _format_expiration_date(
128
+ getattr(payment_method, "expiration_year", card_spec["expiration_year"]),
129
+ getattr(payment_method, "expiration_month", card_spec["expiration_month"]),
130
+ ),
131
+ "payment_profile_id": payment_method.token,
132
+ "customer_profile_id": payment_method.customer_id,
133
+ "email_address": user["email_address"],
134
+ "first_name": user.get("first_name")
135
+ or user["email_address"].split("@")[0],
136
+ "last_name": user.get("last_name") or "Demo",
137
+ "is_default": getattr(payment_method, "default", True),
138
+ },
139
+ )
96
140
 
97
141
  if customer is None:
98
142
  result = gateway.customer.create(
@@ -107,26 +151,29 @@ def _get_demo_braintree_card(tx, user: Dict) -> Dict:
107
151
  raise RuntimeError(result.message)
108
152
  customer = result.customer
109
153
 
110
- payment_method_result = gateway.payment_method.create(
154
+ payment_method_result = gateway.credit_card.create(
111
155
  {
112
156
  "customer_id": customer.id,
113
- "payment_method_nonce": _DEMO_BRAINTREE_NONCE,
157
+ "number": card_spec["number"],
158
+ "expiration_date": card_spec["expiration_date"],
159
+ "cvv": "123",
160
+ "billing_address": {"postal_code": card_spec["postal_code"]},
114
161
  "options": {"make_default": True, "verify_card": True},
115
162
  }
116
163
  )
117
164
  if not payment_method_result.is_success:
118
165
  raise RuntimeError(payment_method_result.message)
119
166
 
120
- payment_method = payment_method_result.payment_method
167
+ payment_method = payment_method_result.credit_card
121
168
  return _save_payment_profile(
122
169
  tx,
123
170
  {
124
171
  "src": "BT",
125
172
  "card_type": getattr(payment_method, "card_type", "Visa"),
126
- "card_number": getattr(payment_method, "last_4", "1111"),
173
+ "card_number": getattr(payment_method, "last_4", card_spec["last4"]),
127
174
  "expiration_date": _format_expiration_date(
128
- getattr(payment_method, "expiration_year", "2030"),
129
- getattr(payment_method, "expiration_month", "12"),
175
+ getattr(payment_method, "expiration_year", card_spec["expiration_year"]),
176
+ getattr(payment_method, "expiration_month", card_spec["expiration_month"]),
130
177
  ),
131
178
  "payment_profile_id": payment_method.token,
132
179
  "customer_profile_id": customer.id,
@@ -289,6 +336,7 @@ def resolve_demo_charge_cards(tx, user: Dict, payment_processor: str, cards: Lis
289
336
  if demo_card:
290
337
  merged_card = dict(card)
291
338
  merged_card.update(demo_card)
339
+ merged_card["lookup_id"] = demo_card.get("payment_profile_id")
292
340
  resolved_cards.append(merged_card)
293
341
  else:
294
342
  resolved_cards.append(card)
@@ -299,6 +347,7 @@ def resolve_demo_charge_cards(tx, user: Dict, payment_processor: str, cards: Lis
299
347
  for source in get_payment_profile_sources(payment_processor):
300
348
  demo_card = get_demo_card_for_source(tx, user, source)
301
349
  if demo_card:
350
+ demo_card["lookup_id"] = demo_card.get("payment_profile_id")
302
351
  return [demo_card]
303
352
 
304
353
  return cards
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.232
3
+ Version: 0.0.234
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -133,6 +133,7 @@ src/velocity/payment/__init__.py
133
133
  src/velocity/payment/authorizenet_adapter.py
134
134
  src/velocity/payment/base_adapter.py
135
135
  src/velocity/payment/braintree_adapter.py
136
+ src/velocity/payment/charge_rules.py
136
137
  src/velocity/payment/demo_profiles.py
137
138
  src/velocity/payment/profiles.py
138
139
  src/velocity/payment/router.py