spree_core 5.3.4 → 5.4.0.beta

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 (239) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/orders/find_complete.rb +33 -2
  3. data/app/finders/spree/stores/find_default.rb +17 -0
  4. data/app/finders/spree/variants/visible_finder.rb +1 -0
  5. data/app/helpers/spree/addresses_helper.rb +1 -2
  6. data/app/helpers/spree/base_helper.rb +5 -4
  7. data/app/jobs/spree/api_key_touch_job.rb +9 -0
  8. data/app/models/concerns/spree/admin_user_methods.rb +32 -0
  9. data/app/models/concerns/spree/number_as_param.rb +5 -3
  10. data/app/models/concerns/spree/prefixed_id.rb +82 -0
  11. data/app/models/concerns/spree/product_scopes.rb +33 -18
  12. data/app/models/concerns/spree/publishable.rb +47 -47
  13. data/app/models/concerns/spree/{multi_store_resource.rb → store_scoped_resource.rb} +1 -10
  14. data/app/models/concerns/spree/stores/markets.rb +124 -0
  15. data/app/models/concerns/spree/user_methods.rb +7 -7
  16. data/app/models/concerns/spree/user_payment_source.rb +2 -0
  17. data/app/models/concerns/spree/user_roles.rb +1 -0
  18. data/app/models/spree/ability.rb +13 -0
  19. data/app/models/spree/address.rb +30 -0
  20. data/app/models/spree/adjustment.rb +2 -0
  21. data/app/models/spree/api_key.rb +47 -0
  22. data/app/models/spree/asset.rb +2 -0
  23. data/app/models/spree/authentication/strategies/base_strategy.rb +55 -0
  24. data/app/models/spree/authentication/strategies/email_password_strategy.rb +47 -0
  25. data/app/models/spree/base.rb +1 -0
  26. data/app/models/spree/calculator.rb +2 -0
  27. data/app/models/spree/country.rb +56 -0
  28. data/app/models/spree/coupon_code.rb +2 -0
  29. data/app/models/spree/credit_card.rb +2 -0
  30. data/app/models/spree/current.rb +9 -4
  31. data/app/models/spree/customer_group.rb +2 -0
  32. data/app/models/spree/customer_return.rb +2 -0
  33. data/app/models/spree/data_feed.rb +2 -0
  34. data/app/models/spree/digital.rb +2 -0
  35. data/app/models/spree/digital_link.rb +2 -0
  36. data/app/models/spree/export.rb +3 -2
  37. data/app/models/spree/fulfilment_changer.rb +8 -2
  38. data/app/models/spree/gateway/bogus.rb +60 -0
  39. data/app/models/spree/gateway_customer.rb +2 -0
  40. data/app/models/spree/gift_card.rb +2 -0
  41. data/app/models/spree/gift_card_batch.rb +2 -0
  42. data/app/models/spree/image.rb +18 -0
  43. data/app/models/spree/import.rb +5 -3
  44. data/app/models/spree/import_mapping.rb +2 -0
  45. data/app/models/spree/import_row.rb +2 -0
  46. data/app/models/spree/import_schemas/customers.rb +21 -0
  47. data/app/models/spree/imports/customers.rb +9 -0
  48. data/app/models/spree/integration.rb +2 -0
  49. data/app/models/spree/inventory_unit.rb +2 -0
  50. data/app/models/spree/invitation.rb +6 -6
  51. data/app/models/spree/legacy_admin_user.rb +31 -0
  52. data/app/models/spree/legacy_user.rb +19 -1
  53. data/app/models/spree/line_item.rb +15 -4
  54. data/app/models/spree/log_entry.rb +2 -0
  55. data/app/models/spree/market.rb +83 -0
  56. data/app/models/spree/market_country.rb +25 -0
  57. data/app/models/spree/metafield.rb +2 -0
  58. data/app/models/spree/metafield_definition.rb +2 -0
  59. data/app/models/spree/newsletter_subscriber.rb +2 -0
  60. data/app/models/spree/option_type.rb +2 -0
  61. data/app/models/spree/option_value.rb +2 -0
  62. data/app/models/spree/order/address_book.rb +2 -1
  63. data/app/models/spree/order.rb +73 -45
  64. data/app/models/spree/payment.rb +6 -1
  65. data/app/models/spree/payment_capture_event.rb +2 -0
  66. data/app/models/spree/payment_method.rb +59 -1
  67. data/app/models/spree/payment_session.rb +109 -0
  68. data/app/models/spree/payment_sessions/bogus.rb +4 -0
  69. data/app/models/spree/payment_setup_session.rb +79 -0
  70. data/app/models/spree/payment_source.rb +2 -0
  71. data/app/models/spree/permission_sets/default_customer.rb +19 -0
  72. data/app/models/spree/policy.rb +2 -0
  73. data/app/models/spree/post.rb +2 -0
  74. data/app/models/spree/post_category.rb +2 -0
  75. data/app/models/spree/price.rb +26 -2
  76. data/app/models/spree/price_list.rb +2 -0
  77. data/app/models/spree/price_rule.rb +2 -0
  78. data/app/models/spree/price_rules/market_rule.rb +19 -0
  79. data/app/models/spree/product.rb +41 -29
  80. data/app/models/spree/promotion/rules/country.rb +1 -1
  81. data/app/models/spree/promotion.rb +3 -1
  82. data/app/models/spree/promotion_action.rb +2 -0
  83. data/app/models/spree/promotion_category.rb +2 -0
  84. data/app/models/spree/promotion_rule.rb +2 -0
  85. data/app/models/spree/prototype.rb +2 -0
  86. data/app/models/spree/refund.rb +2 -0
  87. data/app/models/spree/refund_reason.rb +2 -0
  88. data/app/models/spree/reimbursement/credit.rb +2 -0
  89. data/app/models/spree/reimbursement.rb +2 -0
  90. data/app/models/spree/reimbursement_type.rb +2 -0
  91. data/app/models/spree/report.rb +3 -1
  92. data/app/models/spree/return_authorization.rb +2 -0
  93. data/app/models/spree/return_authorization_reason.rb +2 -0
  94. data/app/models/spree/return_item.rb +2 -0
  95. data/app/models/spree/role.rb +2 -0
  96. data/app/models/spree/shipment.rb +11 -1
  97. data/app/models/spree/shipping_category.rb +2 -0
  98. data/app/models/spree/shipping_method.rb +2 -0
  99. data/app/models/spree/shipping_method_category.rb +2 -0
  100. data/app/models/spree/shipping_rate.rb +2 -0
  101. data/app/models/spree/state_change.rb +2 -0
  102. data/app/models/spree/stock_item.rb +2 -0
  103. data/app/models/spree/stock_location.rb +2 -0
  104. data/app/models/spree/stock_movement.rb +2 -0
  105. data/app/models/spree/stock_transfer.rb +2 -1
  106. data/app/models/spree/store.rb +110 -179
  107. data/app/models/spree/store_credit.rb +2 -0
  108. data/app/models/spree/store_credit_category.rb +2 -0
  109. data/app/models/spree/store_credit_event.rb +2 -0
  110. data/app/models/spree/store_credit_type.rb +2 -0
  111. data/app/models/spree/store_product.rb +2 -0
  112. data/app/models/spree/tax_category.rb +2 -0
  113. data/app/models/spree/tax_rate.rb +2 -0
  114. data/app/models/spree/taxon.rb +4 -1
  115. data/app/models/spree/taxon_image.rb +8 -0
  116. data/app/models/spree/taxon_rule.rb +2 -0
  117. data/app/models/spree/taxonomy.rb +2 -0
  118. data/app/models/spree/user_identity.rb +81 -0
  119. data/app/models/spree/variant.rb +13 -3
  120. data/app/models/spree/webhook_delivery.rb +2 -0
  121. data/app/models/spree/webhook_endpoint.rb +2 -0
  122. data/app/models/spree/wished_item.rb +15 -0
  123. data/app/models/spree/wishlist.rb +2 -8
  124. data/app/models/spree/zone.rb +2 -6
  125. data/app/presenters/spree/filters/price_presenter.rb +1 -0
  126. data/app/presenters/spree/filters/price_range_presenter.rb +1 -0
  127. data/app/presenters/spree/filters/quantified_price_range_presenter.rb +1 -0
  128. data/app/presenters/spree/product_summary_presenter.rb +1 -0
  129. data/app/services/spree/addresses/helper.rb +22 -3
  130. data/app/services/spree/cart/associate.rb +19 -6
  131. data/app/services/spree/checkout/select_shipping_method.rb +13 -1
  132. data/app/services/spree/classifications/reposition.rb +5 -0
  133. data/app/services/spree/imports/row_processors/customer.rb +70 -0
  134. data/app/services/spree/orders/approve.rb +5 -3
  135. data/app/services/spree/orders/cancel.rb +9 -4
  136. data/app/services/spree/products/prepare_nested_attributes.rb +1 -1
  137. data/app/services/spree/sample_data/import_runner.rb +54 -0
  138. data/app/services/spree/sample_data/loader.rb +78 -0
  139. data/app/services/spree/seeds/admin_user.rb +2 -3
  140. data/app/services/spree/seeds/all.rb +1 -0
  141. data/app/services/spree/seeds/api_keys.rb +16 -0
  142. data/app/services/spree/seeds/stores.rb +2 -4
  143. data/app/sorters/spree/orders/sort.rb +4 -0
  144. data/app/subscribers/spree/product_metrics_subscriber.rb +4 -4
  145. data/config/brakeman.ignore +120 -0
  146. data/config/locales/en.yml +20 -1
  147. data/db/migrate/20250923141900_create_spree_user_identities.rb +17 -0
  148. data/db/migrate/20260123000000_create_spree_api_keys.rb +19 -0
  149. data/db/migrate/20260131000000_add_thumbnail_id_to_spree_variants_and_products.rb +9 -0
  150. data/db/migrate/20260213000000_create_spree_payment_sessions.rb +27 -0
  151. data/db/migrate/20260218000000_create_spree_payment_setup_sessions.rb +24 -0
  152. data/db/migrate/20260220000000_create_spree_markets.rb +29 -0
  153. data/db/sample_data/customers.csv +21 -0
  154. data/db/sample_data/metafield_definitions.rb +7 -0
  155. data/db/sample_data/orders.rb +131 -0
  156. data/db/sample_data/payment_methods.rb +17 -0
  157. data/db/sample_data/posts.rb +7 -0
  158. data/db/sample_data/products.csv +1083 -0
  159. data/db/sample_data/promotions.rb +8 -0
  160. data/db/sample_data/shipping_methods.rb +39 -0
  161. data/lib/generators/spree/authentication/devise/devise_generator.rb +2 -2
  162. data/lib/generators/spree/authentication/dummy/dummy_generator.rb +54 -0
  163. data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +52 -0
  164. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +33 -0
  165. data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
  166. data/lib/spree/core/configuration.rb +1 -0
  167. data/lib/spree/core/controller_helpers/common.rb +6 -0
  168. data/lib/spree/core/controller_helpers/currency.rb +5 -0
  169. data/lib/spree/core/controller_helpers/order.rb +5 -1
  170. data/lib/spree/core/controller_helpers/store.rb +1 -1
  171. data/lib/spree/core/dependencies.rb +1 -1
  172. data/lib/spree/core/engine.rb +17 -4
  173. data/lib/spree/core/pricing/context.rb +6 -3
  174. data/lib/spree/core/product_filters.rb +14 -0
  175. data/lib/spree/core/query_filters/comparable.rb +4 -0
  176. data/lib/spree/core/query_filters/text.rb +4 -0
  177. data/lib/spree/core/version.rb +1 -1
  178. data/lib/spree/core.rb +4 -4
  179. data/lib/spree/database_type_utilities.rb +7 -0
  180. data/lib/spree/events.rb +17 -10
  181. data/lib/spree/money.rb +2 -9
  182. data/lib/spree/permitted_attributes.rb +19 -7
  183. data/lib/spree/testing_support/capybara_config.rb +2 -2
  184. data/lib/spree/testing_support/common_rake.rb +15 -4
  185. data/lib/spree/testing_support/factories/api_key_factory.rb +19 -0
  186. data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -5
  187. data/lib/spree/testing_support/factories/import_factory.rb +12 -0
  188. data/lib/spree/testing_support/factories/market_factory.rb +35 -0
  189. data/lib/spree/testing_support/factories/order_factory.rb +3 -1
  190. data/lib/spree/testing_support/factories/payment_method_factory.rb +2 -0
  191. data/lib/spree/testing_support/factories/payment_session_factory.rb +47 -0
  192. data/lib/spree/testing_support/factories/payment_setup_session_factory.rb +31 -0
  193. data/lib/spree/testing_support/factories/price_rule_factory.rb +10 -0
  194. data/lib/spree/testing_support/factories/user_identity_factory.rb +15 -0
  195. data/lib/spree/testing_support/store.rb +3 -2
  196. data/lib/spree/webhooks.rb +7 -7
  197. data/lib/tasks/images.rake +20 -0
  198. data/lib/tasks/markets.rake +40 -0
  199. data/lib/tasks/sample_data.rake +15 -0
  200. data/spec/fixtures/files/customers_import.csv +4 -0
  201. metadata +99 -59
  202. data/LICENSE.md +0 -57
  203. data/app/finders/spree/stores/find_current.rb +0 -28
  204. data/app/models/spree/custom_domain.rb +0 -59
  205. data/app/serializers/spree/events/asset_serializer.rb +0 -22
  206. data/app/serializers/spree/events/base_serializer.rb +0 -61
  207. data/app/serializers/spree/events/customer_return_serializer.rb +0 -20
  208. data/app/serializers/spree/events/digital_link_serializer.rb +0 -20
  209. data/app/serializers/spree/events/digital_serializer.rb +0 -18
  210. data/app/serializers/spree/events/export_serializer.rb +0 -22
  211. data/app/serializers/spree/events/gift_card_batch_serializer.rb +0 -24
  212. data/app/serializers/spree/events/gift_card_serializer.rb +0 -29
  213. data/app/serializers/spree/events/image_serializer.rb +0 -9
  214. data/app/serializers/spree/events/import_row_serializer.rb +0 -23
  215. data/app/serializers/spree/events/import_serializer.rb +0 -24
  216. data/app/serializers/spree/events/invitation_serializer.rb +0 -28
  217. data/app/serializers/spree/events/line_item_serializer.rb +0 -31
  218. data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +0 -21
  219. data/app/serializers/spree/events/order_serializer.rb +0 -39
  220. data/app/serializers/spree/events/payment_serializer.rb +0 -24
  221. data/app/serializers/spree/events/post_category_serializer.rb +0 -20
  222. data/app/serializers/spree/events/post_serializer.rb +0 -26
  223. data/app/serializers/spree/events/price_serializer.rb +0 -22
  224. data/app/serializers/spree/events/product_serializer.rb +0 -24
  225. data/app/serializers/spree/events/promotion_serializer.rb +0 -32
  226. data/app/serializers/spree/events/refund_serializer.rb +0 -23
  227. data/app/serializers/spree/events/reimbursement_serializer.rb +0 -22
  228. data/app/serializers/spree/events/report_serializer.rb +0 -23
  229. data/app/serializers/spree/events/return_authorization_serializer.rb +0 -22
  230. data/app/serializers/spree/events/return_item_serializer.rb +0 -27
  231. data/app/serializers/spree/events/shipment_serializer.rb +0 -24
  232. data/app/serializers/spree/events/stock_item_serializer.rb +0 -22
  233. data/app/serializers/spree/events/stock_movement_serializer.rb +0 -22
  234. data/app/serializers/spree/events/stock_transfer_serializer.rb +0 -22
  235. data/app/serializers/spree/events/store_credit_serializer.rb +0 -30
  236. data/app/serializers/spree/events/user_serializer.rb +0 -18
  237. data/app/serializers/spree/events/variant_serializer.rb +0 -34
  238. data/app/serializers/spree/events/wished_item_serializer.rb +0 -20
  239. data/app/serializers/spree/events/wishlist_serializer.rb +0 -22
@@ -0,0 +1,124 @@
1
+ module Spree
2
+ module Stores
3
+ module Markets
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :markets, class_name: 'Spree::Market', dependent: :destroy
8
+ end
9
+
10
+ # Returns the default market for this store
11
+ # @return [Spree::Market, nil]
12
+ def default_market
13
+ @default_market ||= Spree::Market.default_for_store(self)
14
+ end
15
+
16
+ # Returns the default country, derived from the default market
17
+ # @return [Spree::Country, nil]
18
+ def default_country
19
+ if has_markets?
20
+ default_market&.default_country
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ # Returns the default country ID, derived from the default market
27
+ # @return [Integer, nil]
28
+ def default_country_id
29
+ if has_markets?
30
+ default_country&.id
31
+ else
32
+ read_attribute(:default_country_id)
33
+ end
34
+ end
35
+
36
+ # Returns the default locale, delegating to the default market when markets exist
37
+ # Falls back to the store's own default_locale column
38
+ # @return [String, nil]
39
+ def default_locale
40
+ if has_markets?
41
+ default_market&.default_locale || read_attribute(:default_locale)
42
+ else
43
+ read_attribute(:default_locale)
44
+ end
45
+ end
46
+
47
+ # Returns the default currency, delegating to the default market when markets exist
48
+ # Falls back to the store's own default_currency column
49
+ # @return [String, nil]
50
+ def default_currency
51
+ if has_markets?
52
+ default_market&.currency || read_attribute(:default_currency)
53
+ else
54
+ read_attribute(:default_currency)
55
+ end
56
+ end
57
+
58
+ # Returns the market that contains the given country
59
+ # @param country [Spree::Country]
60
+ # @return [Spree::Market, nil]
61
+ def market_for_country(country)
62
+ Spree::Market.for_country(country, store: self)
63
+ end
64
+
65
+ # Returns countries from all markets as an ActiveRecord relation
66
+ # @return [ActiveRecord::Relation<Spree::Country>]
67
+ def countries_from_markets
68
+ Spree::Country
69
+ .where(id: Spree::Country.joins(market_countries: :market).where(Spree::Market.table_name => { store_id: id, deleted_at: nil }).select(:id))
70
+ .order(:name)
71
+ end
72
+
73
+ # Returns the countries available for checkout, derived from markets
74
+ # @return [Array<Spree::Country>]
75
+ def countries_available_for_checkout
76
+ @countries_available_for_checkout ||= Rails.cache.fetch(countries_available_for_checkout_cache_key) do
77
+ if markets.any?
78
+ markets.flat_map(&:countries).uniq.sort_by(&:name)
79
+ else
80
+ Spree::Country.all.to_a
81
+ end
82
+ end
83
+ end
84
+
85
+ # Returns supported currencies derived from markets, falling back to store attributes
86
+ # @return [Array<Money::Currency>]
87
+ def supported_currencies_list
88
+ @supported_currencies_list ||= if markets.any?
89
+ markets.pluck(:currency).uniq.map do |code|
90
+ ::Money::Currency.find(code)
91
+ end.compact.sort_by { |c| c.iso_code == default_currency ? 0 : 1 }
92
+ else
93
+ legacy_supported_currencies_list
94
+ end
95
+ end
96
+
97
+ # Returns supported locales derived from markets, falling back to store attributes
98
+ # @return [Array<String>]
99
+ def supported_locales_list
100
+ @supported_locales_list ||= if markets.any?
101
+ (markets.flat_map(&:supported_locales_list) << default_locale).compact.uniq.sort
102
+ else
103
+ legacy_supported_locales_list
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def has_markets?
110
+ persisted? && (markets.loaded? ? markets.any? : markets.exists?)
111
+ end
112
+
113
+ def legacy_supported_currencies_list
114
+ ([default_currency] + read_attribute(:supported_currencies).to_s.split(',')).uniq.map(&:to_s).map do |code|
115
+ ::Money::Currency.find(code.strip)
116
+ end.compact.sort_by { |currency| currency.iso_code == default_currency ? 0 : 1 }
117
+ end
118
+
119
+ def legacy_supported_locales_list
120
+ (read_attribute(:supported_locales).to_s.split(',') << default_locale).compact.uniq.sort
121
+ end
122
+ end
123
+ end
124
+ end
@@ -2,16 +2,18 @@ module Spree
2
2
  module UserMethods
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ include Spree::PrefixedId
5
6
  include Spree::Metafields
6
7
  include Spree::UserPaymentSource
7
8
  include Spree::UserReporting
8
9
  include Spree::UserRoles
9
- include Spree::AdminUserMethods
10
10
  include Spree::RansackableAttributes
11
11
  include Spree::MultiSearchable
12
12
  include Spree::Publishable
13
13
 
14
14
  included do
15
+ has_prefix_id :cus # Stripe: cus_
16
+
15
17
  # Enable lifecycle events for user models
16
18
  publishes_lifecycle_events
17
19
 
@@ -40,6 +42,7 @@ module Spree
40
42
  has_many :gift_cards, class_name: 'Spree::GiftCard', foreign_key: :user_id, dependent: :destroy
41
43
  has_many :customer_group_users, class_name: 'Spree::CustomerGroupUser', foreign_key: :user_id, as: :user, dependent: :destroy
42
44
  has_many :customer_groups, through: :customer_group_users, class_name: 'Spree::CustomerGroup'
45
+ has_many :identities, class_name: 'Spree::UserIdentity', as: :user, dependent: :destroy
43
46
  belongs_to :ship_address, class_name: 'Spree::Address', optional: true
44
47
  belongs_to :bill_address, class_name: 'Spree::Address', optional: true
45
48
 
@@ -143,13 +146,10 @@ module Spree
143
146
  end
144
147
 
145
148
  # Returns true if the user can be deleted
149
+ # Customers can be deleted if they have no completed orders
146
150
  # @return [Boolean]
147
151
  def can_be_deleted?
148
- if role_users.where(resource: Spree::Store.current).exists?
149
- Spree::Store.current.users.where.not(id: id).exists?
150
- else
151
- orders.complete.none?
152
- end
152
+ orders.complete.none?
153
153
  end
154
154
 
155
155
  # Returns the CSV row representation of the user
@@ -166,7 +166,7 @@ module Spree
166
166
  end
167
167
 
168
168
  def event_serializer_class
169
- Spree::Events::UserSerializer
169
+ 'Spree::Api::V3::CustomerSerializer'.safe_constantize
170
170
  end
171
171
 
172
172
  def event_prefix
@@ -4,6 +4,8 @@ module Spree
4
4
 
5
5
  included do
6
6
  has_many :credit_cards, class_name: 'Spree::CreditCard', foreign_key: :user_id, dependent: :destroy
7
+ has_many :payment_setup_sessions, class_name: 'Spree::PaymentSetupSession', foreign_key: :customer_id, dependent: :destroy
8
+
7
9
  def default_credit_card
8
10
  credit_cards.default.first
9
11
  end
@@ -48,6 +48,7 @@ module Spree
48
48
  end
49
49
 
50
50
  def self.spree_admin_created?
51
+ Spree::Deprecation.warn('Spree.admin_user_class.spree_admin_created? is deprecated and will be removed in Spree 5.5')
51
52
  spree_admin.exists?
52
53
  end
53
54
 
@@ -30,11 +30,21 @@ module Spree
30
30
  # modify the default +Ability+ of an application. The +ability+ argument must be a class that includes
31
31
  # the +CanCan::Ability+ module. The registered ability should behave properly as a stand-alone class
32
32
  # and therefore should be easy to test in isolation.
33
+ # @deprecated Use Spree::PermissionSets instead. Will be removed in Spree 5.5.
33
34
  def self.register_ability(ability)
35
+ Spree::Deprecation.warn(
36
+ 'Spree::Ability.register_ability is deprecated and will be removed in Spree 5.5. ' \
37
+ 'Please use Spree::PermissionSets instead. See Spree::PermissionSets::Base for details.'
38
+ )
34
39
  abilities.add(ability)
35
40
  end
36
41
 
42
+ # @deprecated Use Spree::PermissionSets instead. Will be removed in Spree 5.5.
37
43
  def self.remove_ability(ability)
44
+ Spree::Deprecation.warn(
45
+ 'Spree::Ability.remove_ability is deprecated and will be removed in Spree 5.5. ' \
46
+ 'Please use Spree::PermissionSets instead. See Spree::PermissionSets::Base for details.'
47
+ )
38
48
  abilities.delete(ability)
39
49
  end
40
50
 
@@ -165,6 +175,9 @@ module Spree
165
175
  wished_item.wishlist.user == user
166
176
  end
167
177
  can :accept, Spree::Invitation, invitee_id: [user.id, nil], invitee_type: user.class.name, status: 'pending'
178
+ can :show, ::Spree::DigitalLink do |digital_link, token|
179
+ digital_link.token == token
180
+ end
168
181
  can :read, ::Spree::Policy
169
182
  can :read, ::Spree::Page if defined?(Spree::Page)
170
183
  can :read, ::Spree::Post if defined?(Spree::Post)
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Address < Spree.base_class
3
+ has_prefix_id :addr # Spree-specific: address
4
+
3
5
  require 'validates_zipcode'
4
6
 
5
7
  include Spree::Metafields
@@ -48,6 +50,8 @@ module Spree
48
50
 
49
51
  after_initialize :set_default_values, if: -> { new_record? && user.present? }
50
52
 
53
+ before_validation :normalize_country
54
+ before_validation :normalize_state
51
55
  before_validation :clear_invalid_state_entities, if: -> { country.present? }, on: :update
52
56
  before_validation :remove_emoji_and_normalize
53
57
 
@@ -83,6 +87,16 @@ module Spree
83
87
 
84
88
  alias_attribute :postal_code, :zipcode
85
89
 
90
+ # Writer methods for API convenience - these set country/state from ISO/abbr codes
91
+ # The reader methods (country_iso, state_abbr) are delegates to country.iso and state.abbr
92
+ def country_iso=(value)
93
+ @country_iso_input = value
94
+ end
95
+
96
+ def state_abbr=(value)
97
+ @state_abbr_input = value
98
+ end
99
+
86
100
  self.whitelisted_ransackable_attributes = ADDRESS_FIELDS
87
101
  self.whitelisted_ransackable_associations = %w[country state user]
88
102
 
@@ -247,6 +261,22 @@ module Spree
247
261
  self.phone ||= user.phone
248
262
  end
249
263
 
264
+ def normalize_country
265
+ iso = @country_iso_input
266
+ return if iso.blank?
267
+
268
+ self.country = Spree::Country.by_iso(iso)
269
+ @country_iso_input = nil
270
+ end
271
+
272
+ def normalize_state
273
+ abbr = @state_abbr_input
274
+ return if abbr.blank? || country.blank?
275
+
276
+ self.state = country.states.find_by(abbr: abbr)
277
+ @state_abbr_input = nil
278
+ end
279
+
250
280
  def clear_state
251
281
  self.state = nil
252
282
  end
@@ -22,6 +22,8 @@
22
22
  # it might be reinstated.
23
23
  module Spree
24
24
  class Adjustment < Spree.base_class
25
+ has_prefix_id :adj # Spree-specific: adjustment
26
+
25
27
  with_options polymorphic: true do
26
28
  belongs_to :adjustable, touch: true
27
29
  belongs_to :source
@@ -0,0 +1,47 @@
1
+ module Spree
2
+ class ApiKey < Spree.base_class
3
+ has_prefix_id :key # Spree-specific: api key
4
+
5
+ KEY_TYPES = %w[publishable secret].freeze
6
+ PREFIXES = { 'publishable' => 'pk_', 'secret' => 'sk_' }.freeze
7
+ TOKEN_LENGTH = 24
8
+
9
+ belongs_to :store, class_name: 'Spree::Store'
10
+ belongs_to :created_by, polymorphic: true, optional: true
11
+ belongs_to :revoked_by, polymorphic: true, optional: true
12
+
13
+ validates :name, presence: true
14
+ validates :key_type, presence: true, inclusion: { in: KEY_TYPES }
15
+ validates :token, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
16
+ validates :store, presence: true
17
+
18
+ before_validation :generate_token, on: :create
19
+
20
+ scope :active, -> { where(revoked_at: nil) }
21
+ scope :revoked, -> { where.not(revoked_at: nil) }
22
+ scope :publishable, -> { where(key_type: 'publishable') }
23
+ scope :secret, -> { where(key_type: 'secret') }
24
+
25
+ def publishable?
26
+ key_type == 'publishable'
27
+ end
28
+
29
+ def secret?
30
+ key_type == 'secret'
31
+ end
32
+
33
+ def active?
34
+ revoked_at.nil?
35
+ end
36
+
37
+ def revoke!(user = nil)
38
+ update!(revoked_at: Time.current, revoked_by: user)
39
+ end
40
+
41
+ private
42
+
43
+ def generate_token
44
+ self.token ||= "#{PREFIXES[key_type]}#{SecureRandom.base58(TOKEN_LENGTH)}"
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Asset < Spree.base_class
3
+ has_prefix_id :asset # Stripe: file_
4
+
3
5
  include Support::ActiveStorage
4
6
  include Spree::Metafields
5
7
  include Spree::Metadata
@@ -0,0 +1,55 @@
1
+ module Spree
2
+ module Authentication
3
+ module Strategies
4
+ class BaseStrategy
5
+ attr_reader :params, :request_env, :user_class
6
+
7
+ def initialize(params:, request_env:, user_class: nil)
8
+ @params = params
9
+ @request_env = request_env
10
+ @user_class = user_class || Spree.user_class
11
+ end
12
+
13
+ # Returns Result object with user on success
14
+ # @return [Spree::ServiceModule::Result]
15
+ def authenticate
16
+ raise NotImplementedError, 'Subclass must implement #authenticate'
17
+ end
18
+
19
+ # Returns provider identifier (e.g., 'google', 'email')
20
+ # @return [String]
21
+ def provider
22
+ raise NotImplementedError, 'Subclass must implement #provider'
23
+ end
24
+
25
+ protected
26
+
27
+ # Success result with user
28
+ def success(user)
29
+ Spree::ServiceModule::Result.new(success: true, value: user)
30
+ end
31
+
32
+ # Failure result with error message
33
+ def failure(message)
34
+ Spree::ServiceModule::Result.new(success: false, error: message)
35
+ end
36
+
37
+ # Find user by email
38
+ def find_user_by_email(email)
39
+ user_class.find_by(email: email)
40
+ end
41
+
42
+ # Find or create user identity
43
+ def find_or_create_user_from_oauth(provider:, uid:, info:, tokens: {})
44
+ Spree::UserIdentity.find_or_create_from_oauth(
45
+ provider: provider,
46
+ uid: uid,
47
+ info: info,
48
+ tokens: tokens,
49
+ user_class: user_class
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ module Spree
2
+ module Authentication
3
+ module Strategies
4
+ class EmailPasswordStrategy < BaseStrategy
5
+ def authenticate
6
+ email = params[:email]
7
+ password = params[:password]
8
+
9
+ return failure('Email is required') if email.blank?
10
+ return failure('Password is required') if password.blank?
11
+
12
+ user = find_user_by_email(email)
13
+ return failure('Invalid email or password') unless user
14
+
15
+ if validate_password(user, password)
16
+ success(user)
17
+ else
18
+ failure('Invalid email or password')
19
+ end
20
+ rescue => e
21
+ Rails.logger.error "EmailPasswordStrategy authentication failed: #{e.message}"
22
+ failure('Authentication failed')
23
+ end
24
+
25
+ def provider
26
+ 'email'
27
+ end
28
+
29
+ private
30
+
31
+ def validate_password(user, password)
32
+ # Try Devise's valid_password? method first (most common)
33
+ if user.respond_to?(:valid_password?)
34
+ user.valid_password?(password)
35
+ # Fallback to authenticate method (for has_secure_password)
36
+ elsif user.respond_to?(:authenticate)
37
+ user.authenticate(password).present?
38
+ else
39
+ # No password authentication available
40
+ Rails.logger.warn "User class #{user.class} does not implement password authentication"
41
+ false
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -4,6 +4,7 @@ class Spree::Base < ApplicationRecord
4
4
  include Spree::TranslatableResourceScopes
5
5
  include Spree::IntegrationsConcern
6
6
  include Spree::Publishable
7
+ include Spree::PrefixedId
7
8
 
8
9
  after_initialize do
9
10
  if has_attribute?(:preferences) && !preferences.nil?
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Calculator < Spree.base_class
3
+ has_prefix_id :calc
4
+
3
5
  acts_as_paranoid
4
6
 
5
7
  belongs_to :calculable, polymorphic: true, optional: true, inverse_of: :calculator, touch: true
@@ -11,6 +11,8 @@ module Spree
11
11
  dependent: :destroy,
12
12
  foreign_key: :zoneable_id
13
13
  has_many :zones, through: :zone_members, class_name: 'Spree::Zone'
14
+ has_many :market_countries, class_name: 'Spree::MarketCountry', dependent: :destroy
15
+ has_many :markets, through: :market_countries, class_name: 'Spree::Market'
14
16
 
15
17
  validates :name, :iso_name, :iso, :iso3, presence: true, uniqueness: { case_sensitive: false, scope: spree_base_uniqueness_scope }
16
18
 
@@ -23,6 +25,42 @@ module Spree
23
25
  self == store.default_country
24
26
  end
25
27
 
28
+ def self.to_tom_select_json
29
+ pluck(:name, :id, :iso).map do |name, id, iso|
30
+ {
31
+ id: id,
32
+ name: "#{iso_to_emoji_flag(iso)} #{name}"
33
+ }
34
+ end.as_json
35
+ end
36
+
37
+ def self.iso_to_emoji_flag(iso)
38
+ iso.upcase.chars.map { |c| (c.ord + 127397).chr(Encoding::UTF_8) }.join
39
+ end
40
+
41
+ # Returns the currency for this country from its market in the current store.
42
+ # Looks up which market contains this country and returns that market's currency.
43
+ #
44
+ # @return [String, nil] currency code (e.g., 'USD', 'EUR') or nil if no market found
45
+ def market_currency
46
+ Spree::Current.store&.market_for_country(self)&.currency
47
+ end
48
+
49
+ # Returns the default locale for this country from its market in the current store.
50
+ # Looks up which market contains this country and returns that market's default locale.
51
+ #
52
+ # @return [String, nil] locale code (e.g., 'en', 'de') or nil if no market found
53
+ def market_locale
54
+ Spree::Current.store&.market_for_country(self)&.default_locale
55
+ end
56
+
57
+ # Returns the supported locales for this country from its market in the current store.
58
+ #
59
+ # @return [Array<String>] locale codes (e.g., ['en', 'fr']) or empty array if no market found
60
+ def market_supported_locales
61
+ Spree::Current.store&.market_for_country(self)&.supported_locales_list || []
62
+ end
63
+
26
64
  def <=>(other)
27
65
  name <=> other.name
28
66
  end
@@ -30,5 +68,23 @@ module Spree
30
68
  def to_s
31
69
  name
32
70
  end
71
+
72
+ # Returns the default currency code for this country (e.g., 'USD', 'EUR')
73
+ # Uses the countries gem (ISO3166) for accurate currency data
74
+ def default_currency
75
+ iso3166_country&.currency_code
76
+ end
77
+
78
+ # Returns the default locale/language for this country (e.g., 'en', 'de')
79
+ # Uses the countries gem (ISO3166) for accurate language data
80
+ def default_locale
81
+ iso3166_country&.languages&.first
82
+ end
83
+
84
+ private
85
+
86
+ def iso3166_country
87
+ @iso3166_country ||= ISO3166::Country.new(iso)
88
+ end
33
89
  end
34
90
  end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class CouponCode < Spree.base_class
3
+ has_prefix_id :coupon
4
+
3
5
  include Spree::Security::CouponCodes if defined?(Spree::Security::CouponCodes)
4
6
 
5
7
  enum :state, %i(unused used)
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class CreditCard < Spree.base_class
3
+ has_prefix_id :card # Stripe: card_
4
+
3
5
  include ActiveMerchant::Billing::CreditCardMethods
4
6
  include Spree::Metafields
5
7
  include Spree::Metadata
@@ -1,17 +1,21 @@
1
1
  module Spree
2
2
  class Current < ::ActiveSupport::CurrentAttributes
3
- attribute :store, :currency, :zone, :price_lists, :global_pricing_context
3
+ attribute :store, :market, :currency, :zone, :price_lists, :global_pricing_context
4
4
 
5
5
  def store
6
6
  super || Spree::Store.default
7
7
  end
8
8
 
9
+ def market
10
+ super || store&.default_market
11
+ end
12
+
9
13
  def currency
10
- super || store&.default_currency
14
+ super || market&.currency || store&.default_currency
11
15
  end
12
16
 
13
17
  def zone
14
- super || Spree::Zone.default_tax || store&.checkout_zone
18
+ super || Spree::Zone.default_tax
15
19
  end
16
20
 
17
21
  def price_lists
@@ -26,7 +30,8 @@ module Spree
26
30
  self.global_pricing_context = Spree::Pricing::Context.new(
27
31
  currency: currency,
28
32
  store: store,
29
- zone: zone
33
+ zone: zone,
34
+ market: market
30
35
  )
31
36
  end
32
37
  end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class CustomerGroup < Spree.base_class
3
+ has_prefix_id :cg
4
+
3
5
  acts_as_paranoid
4
6
 
5
7
  #
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class CustomerReturn < Spree.base_class
3
+ has_prefix_id :cr
4
+
3
5
  include Spree::Core::NumberGenerator.new(prefix: 'CR', length: 9)
4
6
  include Spree::NumberIdentifier
5
7
  include Spree::Metafields
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class DataFeed < Spree.base_class
3
+ has_prefix_id :df
4
+
3
5
  belongs_to :store, class_name: 'Spree::Store', foreign_key: 'store_id'
4
6
 
5
7
  scope :for_store, ->(store) { where(store: store) }
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Digital < Spree.base_class
3
+ has_prefix_id :dig
4
+
3
5
  publishes_lifecycle_events
4
6
 
5
7
  belongs_to :variant
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class DigitalLink < Spree.base_class
3
+ has_prefix_id :dl # Spree-specific: digital link
4
+
3
5
  publishes_lifecycle_events
4
6
 
5
7
  if Rails::VERSION::STRING >= '7.1.0'
@@ -2,11 +2,12 @@ require 'csv'
2
2
 
3
3
  module Spree
4
4
  class Export < Spree.base_class
5
+ has_prefix_id :exp
6
+
5
7
  SUPPORTED_FILE_FORMATS = %i[csv].freeze
6
8
 
7
9
  include Spree::SingleStoreResource
8
10
  include Spree::NumberIdentifier
9
- include Spree::NumberAsParam
10
11
  include Spree::VendorConcern if defined?(Spree::VendorConcern)
11
12
 
12
13
  include Spree::Core::NumberGenerator.new(prefix: 'EF')
@@ -58,7 +59,7 @@ module Spree
58
59
  attribute :record_selection, :string, default: 'filtered'
59
60
 
60
61
  def event_serializer_class
61
- Spree::Events::ExportSerializer
62
+ 'Spree::Api::V3::ExportSerializer'.safe_constantize
62
63
  end
63
64
 
64
65
  def done?