velocity-python 0.0.231__tar.gz → 0.0.232__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 (160) hide show
  1. {velocity_python-0.0.231/src/velocity_python.egg-info → velocity_python-0.0.232}/PKG-INFO +1 -1
  2. {velocity_python-0.0.231 → velocity_python-0.0.232}/pyproject.toml +1 -1
  3. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/__init__.py +3 -0
  5. velocity_python-0.0.232/src/velocity/payment/demo_profiles.py +304 -0
  6. {velocity_python-0.0.231 → velocity_python-0.0.232/src/velocity_python.egg-info}/PKG-INFO +1 -1
  7. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  8. velocity_python-0.0.232/tests/test_payment_demo_profiles.py +58 -0
  9. {velocity_python-0.0.231 → velocity_python-0.0.232}/LICENSE +0 -0
  10. {velocity_python-0.0.231 → velocity_python-0.0.232}/README.md +0 -0
  11. {velocity_python-0.0.231 → velocity_python-0.0.232}/setup.cfg +0 -0
  12. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/__init__.py +0 -0
  13. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/invoices.py +0 -0
  14. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/orders.py +0 -0
  15. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/payments.py +0 -0
  16. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/purchase_orders.py +0 -0
  17. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/tests/__init__.py +0 -0
  18. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/tests/test_email_processing.py +0 -0
  19. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
  20. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
  21. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/__init__.py +0 -0
  22. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/amplify.py +0 -0
  23. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/amplify_build.py +0 -0
  24. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/__init__.py +0 -0
  25. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/base_handler.py +0 -0
  26. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/context.py +0 -0
  27. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/context_factory.py +0 -0
  28. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/exceptions.py +0 -0
  29. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  30. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  31. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  32. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  33. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/perf.py +0 -0
  34. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/response.py +0 -0
  35. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  36. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/tests/__init__.py +0 -0
  37. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  38. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  39. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/aws/tests/test_response.py +0 -0
  40. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/__init__.py +0 -0
  41. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/__init__.py +0 -0
  42. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/column.py +0 -0
  43. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/database.py +0 -0
  44. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/decorators.py +0 -0
  45. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/engine.py +0 -0
  46. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/result.py +0 -0
  47. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/row.py +0 -0
  48. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/sequence.py +0 -0
  49. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/table.py +0 -0
  50. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/transaction.py +0 -0
  51. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/core/view.py +0 -0
  52. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/exceptions.py +0 -0
  53. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/__init__.py +0 -0
  54. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/base/__init__.py +0 -0
  55. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/base/initializer.py +0 -0
  56. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/base/operators.py +0 -0
  57. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/base/sql.py +0 -0
  58. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/base/types.py +0 -0
  59. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/mysql/__init__.py +0 -0
  60. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/mysql/operators.py +0 -0
  61. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/mysql/reserved.py +0 -0
  62. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/mysql/sql.py +0 -0
  63. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/mysql/types.py +0 -0
  64. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/postgres/__init__.py +0 -0
  65. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/postgres/operators.py +0 -0
  66. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/postgres/reserved.py +0 -0
  67. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/postgres/sql.py +0 -0
  68. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/postgres/types.py +0 -0
  69. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  70. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlite/operators.py +0 -0
  71. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  72. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlite/sql.py +0 -0
  73. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlite/types.py +0 -0
  74. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  75. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  76. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  77. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  78. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/sqlserver/types.py +0 -0
  79. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/servers/tablehelper.py +0 -0
  80. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/__init__.py +0 -0
  81. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/common_db_test.py +0 -0
  82. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/__init__.py +0 -0
  83. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/common.py +0 -0
  84. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_column.py +0 -0
  85. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  86. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_database.py +0 -0
  87. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  88. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  89. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  90. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_result.py +0 -0
  91. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_row.py +0 -0
  92. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  93. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  94. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  95. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  96. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  97. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_table.py +0 -0
  98. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  99. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  100. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/sql/__init__.py +0 -0
  101. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/sql/common.py +0 -0
  102. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  103. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  104. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  105. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_db_utils.py +0 -0
  106. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_postgres.py +0 -0
  107. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  108. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  109. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_result_caching.py +0 -0
  110. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  111. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  112. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  113. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  114. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_sql_builder.py +0 -0
  115. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_tablehelper.py +0 -0
  116. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/tests/test_view_helper.py +0 -0
  117. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/db/utils.py +0 -0
  118. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/logging.py +0 -0
  119. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/__init__.py +0 -0
  120. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/conv/__init__.py +0 -0
  121. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/conv/iconv.py +0 -0
  122. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/conv/oconv.py +0 -0
  123. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/db.py +0 -0
  124. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/export.py +0 -0
  125. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/format.py +0 -0
  126. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/mail.py +0 -0
  127. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/merge.py +0 -0
  128. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/__init__.py +0 -0
  129. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_db.py +0 -0
  130. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_fix.py +0 -0
  131. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_format.py +0 -0
  132. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_iconv.py +0 -0
  133. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_merge.py +0 -0
  134. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_oconv.py +0 -0
  135. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_original_error.py +0 -0
  136. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tests/test_timer.py +0 -0
  137. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/timer.py +0 -0
  138. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/misc/tools.py +0 -0
  139. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/authorizenet_adapter.py +0 -0
  140. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/base_adapter.py +0 -0
  141. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/braintree_adapter.py +0 -0
  142. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/profiles.py +0 -0
  143. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/router.py +0 -0
  144. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity/payment/stripe_adapter.py +0 -0
  145. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  146. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity_python.egg-info/requires.txt +0 -0
  147. {velocity_python-0.0.231 → velocity_python-0.0.232}/src/velocity_python.egg-info/top_level.txt +0 -0
  148. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_amplify_build.py +0 -0
  149. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_decorators.py +0 -0
  150. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_iconv_money_to_cents.py +0 -0
  151. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_lambda_handler.py +0 -0
  152. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_lambda_handler_auth.py +0 -0
  153. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_mixins_import.py +0 -0
  154. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_payment_braintree_adapter.py +0 -0
  155. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_payment_profiles.py +0 -0
  156. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_payment_router.py +0 -0
  157. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_payment_stripe_adapter.py +0 -0
  158. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  159. {velocity_python-0.0.231 → velocity_python-0.0.232}/tests/test_table_alter.py +0 -0
  160. {velocity_python-0.0.231 → velocity_python-0.0.232}/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.231
3
+ Version: 0.0.232
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.231"
7
+ version = "0.0.232"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.0.231"
1
+ __version__ = version = "0.0.232"
2
2
 
3
3
  from . import aws
4
4
  from . import db
@@ -54,6 +54,7 @@ from .profiles import (
54
54
  build_stripe_payment_profile,
55
55
  upsert_payment_profile,
56
56
  )
57
+ from .demo_profiles import resolve_demo_charge_cards, get_card_lookup_id
57
58
 
58
59
  __all__ = [
59
60
  # Base classes
@@ -80,6 +81,8 @@ __all__ = [
80
81
  "get_payment_profile_sources",
81
82
  "build_stripe_payment_profile",
82
83
  "upsert_payment_profile",
84
+ "resolve_demo_charge_cards",
85
+ "get_card_lookup_id",
83
86
  ]
84
87
 
85
88
  __version__ = "1.0.0"
@@ -0,0 +1,304 @@
1
+ """Demo-only payment profile helpers for charging copied production users safely."""
2
+
3
+ import os
4
+ from typing import Dict, List, Optional
5
+
6
+ from .profiles import get_payment_profile_sources, upsert_payment_profile
7
+ from .router import get_processor_config
8
+
9
+ _DEMO_BRAINTREE_NONCE = "fake-valid-nonce"
10
+ _DEMO_STRIPE_TOKEN = "tok_visa"
11
+ _DEMO_AUTHORIZE_CARD_NUMBER = "4111111111111111"
12
+ _DEMO_AUTHORIZE_EXPIRATION = "2030-12"
13
+ _DEMO_AUTHORIZE_CARD_CODE = "123"
14
+
15
+
16
+ def is_demo_environment() -> bool:
17
+ return str(os.environ.get("USER_BRANCH") or "demo").lower() != "production"
18
+
19
+
20
+ def build_demo_profile_email(email_address: str, processor_code: str) -> str:
21
+ email_address = str(email_address or "")
22
+ if "@" not in email_address:
23
+ return email_address
24
+ local_part, domain = email_address.split("@", 1)
25
+ return f"{local_part}+demo-{processor_code}@{domain}"
26
+
27
+
28
+ def _format_expiration_date(year, month) -> str:
29
+ return f"{year}-{str(month).zfill(2)}"
30
+
31
+
32
+ def _save_payment_profile(tx, data: Dict) -> Dict:
33
+ upsert_payment_profile(
34
+ tx,
35
+ data,
36
+ key={"payment_profile_id": data["payment_profile_id"]},
37
+ )
38
+ tx.commit()
39
+ return data
40
+
41
+
42
+ def get_card_lookup_id(card: Dict) -> str:
43
+ return str(
44
+ card.get("sys_id")
45
+ or card.get("payment_profile_id")
46
+ or card.get("customer_profile_id")
47
+ or card.get("src")
48
+ or "unknown-card"
49
+ )
50
+
51
+
52
+ def _get_demo_braintree_card(tx, user: Dict) -> Dict:
53
+ import braintree
54
+
55
+ config = get_processor_config("braintree")
56
+ environment = (
57
+ braintree.Environment.Production
58
+ if str(config.get("environment") or "sandbox").lower() == "production"
59
+ else braintree.Environment.Sandbox
60
+ )
61
+ gateway = braintree.BraintreeGateway(
62
+ braintree.Configuration(
63
+ environment=environment,
64
+ merchant_id=config["merchant_id"],
65
+ public_key=config["public_key"],
66
+ private_key=config["private_key"],
67
+ )
68
+ )
69
+
70
+ demo_email = build_demo_profile_email(user["email_address"], "bt")
71
+ customer = None
72
+
73
+ collection = gateway.customer.search(braintree.CustomerSearch.email == demo_email)
74
+ 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
+ )
96
+
97
+ if customer is None:
98
+ result = gateway.customer.create(
99
+ {
100
+ "email": demo_email,
101
+ "first_name": user.get("first_name")
102
+ or user["email_address"].split("@")[0],
103
+ "last_name": user.get("last_name") or "Demo",
104
+ }
105
+ )
106
+ if not result.is_success:
107
+ raise RuntimeError(result.message)
108
+ customer = result.customer
109
+
110
+ payment_method_result = gateway.payment_method.create(
111
+ {
112
+ "customer_id": customer.id,
113
+ "payment_method_nonce": _DEMO_BRAINTREE_NONCE,
114
+ "options": {"make_default": True, "verify_card": True},
115
+ }
116
+ )
117
+ if not payment_method_result.is_success:
118
+ raise RuntimeError(payment_method_result.message)
119
+
120
+ payment_method = payment_method_result.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", "1111"),
127
+ "expiration_date": _format_expiration_date(
128
+ getattr(payment_method, "expiration_year", "2030"),
129
+ getattr(payment_method, "expiration_month", "12"),
130
+ ),
131
+ "payment_profile_id": payment_method.token,
132
+ "customer_profile_id": 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": True,
138
+ },
139
+ )
140
+
141
+
142
+ def _get_demo_stripe_card(tx, user: Dict) -> Dict:
143
+ import stripe
144
+
145
+ stripe_config = get_processor_config("stripe")
146
+ api_key = stripe_config["api_key"]
147
+ demo_email = build_demo_profile_email(user["email_address"], "st")
148
+ full_name = user.get("full_name") or user["email_address"]
149
+
150
+ customers = stripe.Customer.list(email=demo_email, limit=1, api_key=api_key)
151
+ if customers.data:
152
+ customer = customers.data[0]
153
+ else:
154
+ customer = stripe.Customer.create(
155
+ email=demo_email,
156
+ name=full_name,
157
+ metadata={
158
+ "platform": "caringcent",
159
+ "environment": "demo",
160
+ "source_email": user["email_address"],
161
+ },
162
+ api_key=api_key,
163
+ )
164
+
165
+ payment_methods = stripe.PaymentMethod.list(
166
+ customer=customer.id, type="card", limit=1, api_key=api_key
167
+ )
168
+ if payment_methods.data:
169
+ payment_method = payment_methods.data[0]
170
+ else:
171
+ payment_method = stripe.PaymentMethod.create(
172
+ type="card",
173
+ card={"token": _DEMO_STRIPE_TOKEN},
174
+ billing_details={"email": demo_email, "name": full_name},
175
+ api_key=api_key,
176
+ )
177
+ payment_method = stripe.PaymentMethod.attach(
178
+ payment_method.id, customer=customer.id, api_key=api_key
179
+ )
180
+ stripe.Customer.modify(
181
+ customer.id,
182
+ invoice_settings={"default_payment_method": payment_method.id},
183
+ api_key=api_key,
184
+ )
185
+
186
+ return _save_payment_profile(
187
+ tx,
188
+ {
189
+ "src": "ST",
190
+ "card_type": getattr(payment_method.card, "brand", "visa"),
191
+ "card_number": getattr(payment_method.card, "last4", "4242"),
192
+ "expiration_date": _format_expiration_date(
193
+ getattr(payment_method.card, "exp_year", "2030"),
194
+ getattr(payment_method.card, "exp_month", "12"),
195
+ ),
196
+ "payment_profile_id": payment_method.id,
197
+ "customer_profile_id": customer.id,
198
+ "email_address": user["email_address"],
199
+ "first_name": user.get("first_name")
200
+ or user["email_address"].split("@")[0],
201
+ "last_name": user.get("last_name") or "Demo",
202
+ "is_default": True,
203
+ },
204
+ )
205
+
206
+
207
+ def _get_demo_authorize_card(tx, user: Dict) -> Dict:
208
+ from .authorizenet_adapter import AuthorizeNetAdapter
209
+
210
+ adapter = AuthorizeNetAdapter(get_processor_config("authorizenet"))
211
+ demo_email = build_demo_profile_email(user["email_address"], "an")
212
+ customer_result = adapter.get_or_create_customer_profile(
213
+ tx,
214
+ {
215
+ "email_address": demo_email,
216
+ "name": user.get("full_name") or user["email_address"],
217
+ },
218
+ )
219
+ customer_profile_id = customer_result["customer_profile_id"]
220
+ existing = (
221
+ tx.table("payment_profiles")
222
+ .select(
223
+ where={
224
+ "src": "AN",
225
+ "email_address": user["email_address"],
226
+ "customer_profile_id": customer_profile_id,
227
+ },
228
+ orderby="is_default desc, sys_id desc",
229
+ )
230
+ .as_dict()
231
+ .all()
232
+ )
233
+ if existing:
234
+ return existing[0]
235
+
236
+ payment_result = adapter.attach_payment_method(
237
+ tx,
238
+ customer_profile_id,
239
+ "",
240
+ {
241
+ "card_number": _DEMO_AUTHORIZE_CARD_NUMBER,
242
+ "expiration_date": _DEMO_AUTHORIZE_EXPIRATION,
243
+ "card_code": _DEMO_AUTHORIZE_CARD_CODE,
244
+ "first_name": user.get("first_name")
245
+ or user["email_address"].split("@")[0],
246
+ "last_name": user.get("last_name") or "Demo",
247
+ "set_default": True,
248
+ },
249
+ )
250
+ return _save_payment_profile(
251
+ tx,
252
+ {
253
+ "src": "AN",
254
+ "card_type": "Visa",
255
+ "card_number": _DEMO_AUTHORIZE_CARD_NUMBER[-4:],
256
+ "expiration_date": _DEMO_AUTHORIZE_EXPIRATION,
257
+ "payment_profile_id": payment_result["payment_profile_id"],
258
+ "customer_profile_id": customer_profile_id,
259
+ "email_address": user["email_address"],
260
+ "first_name": user.get("first_name")
261
+ or user["email_address"].split("@")[0],
262
+ "last_name": user.get("last_name") or "Demo",
263
+ "is_default": True,
264
+ },
265
+ )
266
+
267
+
268
+ def get_demo_card_for_source(tx, user: Dict, source: str) -> Optional[Dict]:
269
+ if source == "BT":
270
+ return _get_demo_braintree_card(tx, user)
271
+ if source == "ST":
272
+ return _get_demo_stripe_card(tx, user)
273
+ if source == "AN":
274
+ return _get_demo_authorize_card(tx, user)
275
+ return None
276
+
277
+
278
+ def resolve_demo_charge_cards(tx, user: Dict, payment_processor: str, cards: List[Dict]):
279
+ if not is_demo_environment():
280
+ return cards
281
+
282
+ resolved_cards = []
283
+ demo_cards_by_source = {}
284
+ for card in cards:
285
+ source = card.get("src")
286
+ if source not in demo_cards_by_source:
287
+ demo_cards_by_source[source] = get_demo_card_for_source(tx, user, source)
288
+ demo_card = demo_cards_by_source[source]
289
+ if demo_card:
290
+ merged_card = dict(card)
291
+ merged_card.update(demo_card)
292
+ resolved_cards.append(merged_card)
293
+ else:
294
+ resolved_cards.append(card)
295
+
296
+ if resolved_cards:
297
+ return resolved_cards
298
+
299
+ for source in get_payment_profile_sources(payment_processor):
300
+ demo_card = get_demo_card_for_source(tx, user, source)
301
+ if demo_card:
302
+ return [demo_card]
303
+
304
+ return cards
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.231
3
+ Version: 0.0.232
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/demo_profiles.py
136
137
  src/velocity/payment/profiles.py
137
138
  src/velocity/payment/router.py
138
139
  src/velocity/payment/stripe_adapter.py
@@ -148,6 +149,7 @@ tests/test_lambda_handler.py
148
149
  tests/test_lambda_handler_auth.py
149
150
  tests/test_mixins_import.py
150
151
  tests/test_payment_braintree_adapter.py
152
+ tests/test_payment_demo_profiles.py
151
153
  tests/test_payment_profiles.py
152
154
  tests/test_payment_router.py
153
155
  tests/test_payment_stripe_adapter.py
@@ -0,0 +1,58 @@
1
+ from velocity.payment.demo_profiles import resolve_demo_charge_cards
2
+
3
+
4
+ def test_resolve_demo_charge_cards_replaces_existing_cards(monkeypatch):
5
+ user = {"email_address": "person@example.com"}
6
+ prod_card = {
7
+ "src": "BT",
8
+ "payment_profile_id": "prod-token",
9
+ "customer_profile_id": "prod-customer",
10
+ }
11
+
12
+ monkeypatch.setattr(
13
+ "velocity.payment.demo_profiles.is_demo_environment", lambda: True
14
+ )
15
+ monkeypatch.setattr(
16
+ "velocity.payment.demo_profiles.get_demo_card_for_source",
17
+ lambda tx, user, source: {
18
+ "src": source,
19
+ "payment_profile_id": "demo-token",
20
+ "customer_profile_id": "demo-customer",
21
+ },
22
+ )
23
+
24
+ resolved = resolve_demo_charge_cards(None, user, "braintree", [prod_card])
25
+
26
+ assert resolved == [
27
+ {
28
+ "src": "BT",
29
+ "payment_profile_id": "demo-token",
30
+ "customer_profile_id": "demo-customer",
31
+ }
32
+ ]
33
+
34
+
35
+ def test_resolve_demo_charge_cards_creates_fallback_card_when_none_exist(monkeypatch):
36
+ user = {"email_address": "person@example.com"}
37
+
38
+ monkeypatch.setattr(
39
+ "velocity.payment.demo_profiles.is_demo_environment", lambda: True
40
+ )
41
+ monkeypatch.setattr(
42
+ "velocity.payment.demo_profiles.get_demo_card_for_source",
43
+ lambda tx, user, source: {
44
+ "src": source,
45
+ "payment_profile_id": f"{source.lower()}-demo-token",
46
+ "customer_profile_id": f"{source.lower()}-demo-customer",
47
+ },
48
+ )
49
+
50
+ resolved = resolve_demo_charge_cards(None, user, "stripe", [])
51
+
52
+ assert resolved == [
53
+ {
54
+ "src": "ST",
55
+ "payment_profile_id": "st-demo-token",
56
+ "customer_profile_id": "st-demo-customer",
57
+ }
58
+ ]