spree_core 5.6.0.rc1 → 5.6.0.rc2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8058b9d81b22cf12abe2193023f52faa57b5d757b99e0359e4592415b00a434
4
- data.tar.gz: 13d6653cb85ab1364290621d668ed291aecbff9442ce205803595fe316ed4ea7
3
+ metadata.gz: df2eef5a82cf40bca9b5c518b663153584a72dc1dadf05880a5c2ce344766394
4
+ data.tar.gz: 48771116e279ccb8c5ed4f83896a60e8063b74cb8785ff51953c9d8446a804f2
5
5
  SHA512:
6
- metadata.gz: ecbab7dc49344e7c228237344ff01b7addacb6309e0e70cc6a42510823265cfd97827c2a00fb9c990a90b6ace535529b2d60e11c0b45bce745d3185a8422851a
7
- data.tar.gz: a2657923c327254ee18b0b222aff4b6b7c3075b26d4c9ff001f66d3b2ad5b35f7daf6d36b5d16161cfaf62613707722ae8dcf55549c2a5c811c3333638240bab
6
+ metadata.gz: 3a27318377cb47ae53d0405a9a034642119446d4507f03c68966cd550cf41430dcdb0cc3ff7c67f8bf1435b695816c26f92b73e525f2ead42eaf83070c019f40
7
+ data.tar.gz: 1ac22bf54e39352eede750b630b7e2d6ca6a0d4713c776f0efd20e9cf552f3689b24b154c0694db3ae0edabba37a0bc1c7ad91aa5a0e84cca62aa5cc459d48f7
@@ -59,6 +59,9 @@ module Spree
59
59
  before_validation :clone_billing_address, if: :use_billing?
60
60
  before_destroy :check_completed_orders
61
61
 
62
+ after_save_commit :sync_newsletter_subscription_with_marketing_consent,
63
+ if: :saved_change_to_accepts_email_marketing?
64
+
62
65
  attr_accessor :use_billing
63
66
 
64
67
  has_person_name
@@ -281,6 +284,14 @@ module Spree
281
284
  end
282
285
  end
283
286
 
287
+ # @param store [Spree::Store] store to scope the lookup to; defaults to the current store
288
+ # @return [Spree::NewsletterSubscriber, nil] this user's newsletter subscriber for the given store
289
+ def newsletter_subscriber(store = Spree::Current.store)
290
+ return unless store
291
+
292
+ Spree::NewsletterSubscriber.for_store(store).find_by(user_id: id)
293
+ end
294
+
284
295
  private
285
296
 
286
297
  def check_completed_orders
@@ -308,5 +319,14 @@ module Spree
308
319
  self.login = email
309
320
  save
310
321
  end
322
+
323
+ def sync_newsletter_subscription_with_marketing_consent
324
+ if accepts_email_marketing?
325
+ Spree::NewsletterSubscriber.subscribe(email: email, user: self)
326
+ else
327
+ # Only remove the subscriber for the current store, mirroring the store-scoped subscribe path.
328
+ newsletter_subscriber&.destroy
329
+ end
330
+ end
311
331
  end
312
332
  end
@@ -9,6 +9,10 @@ module Spree
9
9
 
10
10
  has_secure_token :verification_token
11
11
 
12
+ generates_token_for :unsubscribe do
13
+ email
14
+ end
15
+
12
16
  #
13
17
  # Associations
14
18
  #
@@ -58,6 +58,9 @@ module Spree
58
58
  # Gift card management - users can view their own gift cards
59
59
  can :read, Spree::GiftCard, user_id: user.id
60
60
 
61
+ # Newsletter subscription management — customers manage their own newsletter subscribers
62
+ can [:show, :destroy], Spree::NewsletterSubscriber, user_id: user.id
63
+
61
64
  # Wishlist management
62
65
  can :manage, Spree::Wishlist, user_id: user.id
63
66
  can :show, Spree::Wishlist do |wishlist|
@@ -5,10 +5,10 @@ module Spree
5
5
  belongs_to :user, polymorphic: true, optional: false
6
6
 
7
7
  validates :provider, presence: true
8
- validates :uid, presence: true, uniqueness: { scope: [:provider, :user_type] }
8
+ validates :uid, presence: true, uniqueness: { scope: %i[provider user_type] }
9
9
 
10
10
  validates :provider, inclusion: {
11
- in: ->(_record) {
11
+ in: lambda { |_record|
12
12
  (Spree.store_authentication_strategies.keys + Spree.admin_authentication_strategies.keys).uniq.map(&:to_s)
13
13
  }
14
14
  }
@@ -27,14 +27,7 @@ module Spree
27
27
  identity = find_by(provider: provider, uid: uid, user_type: user_type)
28
28
 
29
29
  if identity
30
- # Update existing identity with fresh tokens
31
- identity.update(
32
- info: info,
33
- access_token: tokens[:access_token],
34
- refresh_token: tokens[:refresh_token],
35
- expires_at: tokens[:expires_at]
36
- )
37
- identity.user
30
+ refresh_identity(identity, info: info, tokens: tokens).user
38
31
  else
39
32
  # Create new user and identity
40
33
  create_user_from_oauth(
@@ -45,28 +38,46 @@ module Spree
45
38
  user_class: user_class
46
39
  )
47
40
  end
41
+ rescue ActiveRecord::RecordNotUnique
42
+ refresh_identity(
43
+ find_by!(provider: provider, uid: uid, user_type: user_type),
44
+ info: info,
45
+ tokens: tokens
46
+ ).user
48
47
  end
49
48
 
50
- def self.create_user_from_oauth(provider:, uid:, info:, tokens: {}, user_class: nil)
51
- user_class ||= Spree.user_class
52
-
53
- user = user_class.create!(
54
- email: info[:email] || generate_temp_email(provider, uid),
55
- password: SecureRandom.hex(32), # Random password for OAuth users
56
- first_name: info[:first_name],
57
- last_name: info[:last_name]
58
- )
59
-
60
- user.identities.create!(
61
- provider: provider,
62
- uid: uid,
49
+ def self.refresh_identity(identity, info:, tokens: {})
50
+ identity.update(
63
51
  info: info,
64
52
  access_token: tokens[:access_token],
65
53
  refresh_token: tokens[:refresh_token],
66
54
  expires_at: tokens[:expires_at]
67
55
  )
56
+ identity
57
+ end
58
+
59
+ def self.create_user_from_oauth(provider:, uid:, info:, tokens: {}, user_class: nil)
60
+ user_class ||= Spree.user_class
61
+
62
+ transaction do
63
+ user = user_class.create!(
64
+ email: info[:email] || generate_temp_email(provider, uid),
65
+ password: SecureRandom.hex(32), # Random password for OAuth users
66
+ first_name: info[:first_name],
67
+ last_name: info[:last_name]
68
+ )
68
69
 
69
- user
70
+ user.identities.create!(
71
+ provider: provider,
72
+ uid: uid,
73
+ info: info,
74
+ access_token: tokens[:access_token],
75
+ refresh_token: tokens[:refresh_token],
76
+ expires_at: tokens[:expires_at]
77
+ )
78
+
79
+ user
80
+ end
70
81
  end
71
82
 
72
83
  def self.generate_temp_email(provider, uid)
@@ -39,6 +39,7 @@ module Spree
39
39
  id: subscriber.prefixed_id,
40
40
  email: subscriber.email,
41
41
  verification_token: subscriber.verification_token,
42
+ unsubscribe_token: subscriber.generate_token_for(:unsubscribe),
42
43
  store_id: current_store.prefixed_id,
43
44
  customer_id: subscriber.user&.prefixed_id
44
45
  }
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.6.0.rc1'.freeze
2
+ VERSION = '5.6.0.rc2'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/core.rb CHANGED
@@ -180,6 +180,22 @@ module Spree
180
180
  ->(locale) { locale.name == Spree::Current.content_locale }
181
181
  end
182
182
 
183
+ # Stable anonymous identifier for this Spree installation. Generated once,
184
+ # persisted in the preferences store and reused afterwards. It identifies the
185
+ # installation only
186
+ #
187
+ # Deliberately not memoized: the persisted preference is the single source
188
+ # of truth, so processes that race to generate the first value converge on
189
+ # the winning row at the next read instead of each holding a different id
190
+ # for their lifetime.
191
+ #
192
+ # @return [String] UUID
193
+ def self.install_id
194
+ store = Spree::Preferences::Store.instance
195
+ store.get('spree/install_id') { nil }.presence ||
196
+ SecureRandom.uuid.tap { |id| store.set('spree/install_id', id) }
197
+ end
198
+
183
199
  # Used to configure Spree.
184
200
  #
185
201
  # Example:
@@ -94,7 +94,7 @@ FactoryBot.define do
94
94
  factory :completed_order_with_store_credit_payment do
95
95
  after(:create) do |order|
96
96
  store_credit = create(:store_credit, amount: order.total, store: order.store, user: order.user)
97
- payment_method = create(:store_credit_payment_method, stores: [order.store])
97
+ payment_method = create(:store_credit_payment_method, store: order.store)
98
98
 
99
99
  create(:store_credit_payment, amount: order.total, order: order, source: store_credit, payment_method: payment_method)
100
100
  end
@@ -5,7 +5,7 @@ FactoryBot.define do
5
5
  state { 'checkout' }
6
6
  response_code { "BGS-#{SecureRandom.hex(6)}" }
7
7
 
8
- payment_method { create(:credit_card_payment_method, stores: [order.store]) }
8
+ payment_method { create(:credit_card_payment_method, store: order.store) }
9
9
  association(:source, factory: :credit_card)
10
10
 
11
11
  factory :payment_with_refund do
@@ -16,7 +16,7 @@ FactoryBot.define do
16
16
  end
17
17
 
18
18
  factory :custom_payment, class: Spree::Payment do
19
- payment_method { create(:custom_payment_method, stores: [order.store]) }
19
+ payment_method { create(:custom_payment_method, store: order.store) }
20
20
  source { create(:payment_source, user: order.user, payment_method: payment_method) }
21
21
  end
22
22
  end
@@ -29,7 +29,7 @@ FactoryBot.define do
29
29
  end
30
30
 
31
31
  factory :store_credit_payment, class: Spree::Payment, parent: :payment do
32
- payment_method { create(:store_credit_payment_method, stores: [order.store]) }
32
+ payment_method { create(:store_credit_payment_method, store: order.store) }
33
33
  source { create(:store_credit, store: order.store, user: order.user) }
34
34
  end
35
35
  end
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :payment_session, class: 'Spree::PaymentSession' do
3
3
  order
4
- payment_method { create(:credit_card_payment_method, stores: [order.store]) }
4
+ payment_method { create(:credit_card_payment_method, store: order.store) }
5
5
  amount { order.total }
6
6
  currency { order.currency }
7
7
  status { 'pending' }
@@ -39,7 +39,7 @@ FactoryBot.define do
39
39
 
40
40
  factory :bogus_payment_session, class: 'Spree::PaymentSessions::Bogus' do
41
41
  type { 'Spree::PaymentSessions::Bogus' }
42
- payment_method { create(:bogus_payment_method, stores: [order.store]) }
42
+ payment_method { create(:bogus_payment_method, store: order.store) }
43
43
  external_id { "bogus_#{SecureRandom.hex(12)}" }
44
44
  external_data { { 'client_secret' => "bogus_secret_#{SecureRandom.hex(8)}" } }
45
45
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0.rc1
4
+ version: 5.6.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-15 00:00:00.000000000 Z
13
+ date: 2026-07-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -1860,9 +1860,9 @@ licenses:
1860
1860
  - BSD-3-Clause
1861
1861
  metadata:
1862
1862
  bug_tracker_uri: https://github.com/spree/spree/issues
1863
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc1
1863
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc2
1864
1864
  documentation_uri: https://docs.spreecommerce.org/
1865
- source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc1
1865
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc2
1866
1866
  post_install_message:
1867
1867
  rdoc_options: []
1868
1868
  require_paths: