spree_core 5.5.0.rc2 → 5.5.0.rc3
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 +4 -4
- data/app/models/concerns/spree/store_scoped_resource.rb +4 -1
- data/app/models/concerns/spree/typed_associations.rb +8 -1
- data/app/models/spree/ability.rb +7 -4
- data/app/models/spree/allowed_origin.rb +41 -6
- data/app/models/spree/api_key.rb +11 -0
- data/app/models/spree/payment_method.rb +1 -1
- data/app/models/spree/price_list.rb +10 -0
- data/app/models/spree/product/channels.rb +18 -1
- data/app/models/spree/product.rb +39 -3
- data/app/models/spree/promotion.rb +3 -1
- data/app/models/spree/store.rb +3 -13
- data/app/models/spree/store_payment_method.rb +1 -1
- data/app/models/spree/variant.rb +14 -1
- data/app/services/spree/gift_cards/apply.rb +1 -4
- data/app/services/spree/imports/row_processors/product_variant.rb +3 -0
- data/app/services/spree/seeds/payment_methods.rb +10 -7
- data/config/locales/en.yml +3 -0
- data/db/sample_data/payment_methods.rb +18 -18
- data/lib/spree/core/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 710ad2cd963e80f3cd21d2b481246a8a163dc2033fca523292f33ec17fd86525
|
|
4
|
+
data.tar.gz: c59f53d88277a62e55d327cd99c025e8808b30ecf1392ff3ccc9a98ff7cd9026
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67c2f7227e1b20972b41b4dbfceeb2a00cb8c7c21cc0b295fe907d496a581b59b9eb919b41f6a89f6d7f4b19d5c5c6a405733b003915c8e8c6fb4811c15f43a6
|
|
7
|
+
data.tar.gz: 6355791ca10c5e668752f27b3ff7918b3f4ea27bad3b0308f597a310ec518cf139c93d6f562e2da55938524dc381f5a759b4618bc19ad8d684cb7a30841bf821
|
|
@@ -12,7 +12,10 @@ module Spree
|
|
|
12
12
|
|
|
13
13
|
def set_default_store
|
|
14
14
|
return if disable_store_presence_validation?
|
|
15
|
-
|
|
15
|
+
# records built through a store's association (`store.payment_methods.build`)
|
|
16
|
+
# carry their link on the join association — `stores` can't see it until save
|
|
17
|
+
join_association = self.class.reflect_on_association(:stores).through_reflection.name
|
|
18
|
+
return if stores.any? || send(join_association).any?
|
|
16
19
|
|
|
17
20
|
stores << Spree::Store.default
|
|
18
21
|
end
|
|
@@ -54,7 +54,14 @@ module Spree
|
|
|
54
54
|
def reconcile_typed_association(association, rows)
|
|
55
55
|
collection = public_send(association)
|
|
56
56
|
kept_ids = rows.filter_map { |row| save_typed_association_row(collection, row) }
|
|
57
|
-
|
|
57
|
+
if kept_ids.any? || rows.empty?
|
|
58
|
+
collection.where.not(id: kept_ids).destroy_all
|
|
59
|
+
# `where.not(...).destroy_all` deletes the dropped rows from the DB but
|
|
60
|
+
# leaves them in the already-loaded association target. Reset so a
|
|
61
|
+
# same-request read (e.g. the serializer rendering `rule_ids`) reflects
|
|
62
|
+
# the removal instead of echoing ghosts.
|
|
63
|
+
collection.reset
|
|
64
|
+
end
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
def save_typed_association_row(collection, row)
|
data/app/models/spree/ability.rb
CHANGED
|
@@ -44,15 +44,18 @@ module Spree
|
|
|
44
44
|
activate_permission_sets(permission_sets)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# Determines the role names for the current user
|
|
47
|
+
# Determines the role names for the current user, scoped to the current
|
|
48
|
+
# store. A +Spree::RoleUser+ binds a role to a store via +resource+, so a
|
|
49
|
+
# role held on one store does not apply on another.
|
|
48
50
|
#
|
|
49
51
|
# @return [Array<Symbol>] the role names
|
|
50
52
|
def determine_role_names
|
|
51
53
|
return [:default] unless @user.persisted?
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
if @user.respond_to?(:role_users)
|
|
56
|
+
role_names = @user.role_users.where(resource: @store).
|
|
57
|
+
joins(:role).
|
|
58
|
+
pluck("#{Spree::Role.table_name}.name").map(&:to_sym)
|
|
56
59
|
return role_names if role_names.any?
|
|
57
60
|
end
|
|
58
61
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Spree
|
|
4
4
|
class AllowedOrigin < Spree.base_class
|
|
5
|
+
# Loopback/development hosts that match any port, so storing `http://localhost`
|
|
6
|
+
# keeps matching `http://localhost:3000`, `:4000`, etc.
|
|
7
|
+
LOOPBACK_HOSTS = %w[localhost 127.0.0.1 ::1 0.0.0.0].freeze
|
|
8
|
+
|
|
5
9
|
has_prefix_id :ao
|
|
6
10
|
|
|
7
11
|
include Spree::SingleStoreResource
|
|
@@ -12,6 +16,42 @@ module Spree
|
|
|
12
16
|
validates :origin, uniqueness: { scope: [:store_id, *spree_base_uniqueness_scope] }
|
|
13
17
|
validate :origin_must_be_valid_http_url
|
|
14
18
|
|
|
19
|
+
# Parses a URL into its comparable origin components, or nil when the URL is
|
|
20
|
+
# invalid or not http(s). The host is downcased and has a single trailing dot
|
|
21
|
+
# stripped, and the port is the URI default (80/443) when not explicitly given.
|
|
22
|
+
#
|
|
23
|
+
# @param url [String] the URL to parse
|
|
24
|
+
# @return [Hash, nil] `{ scheme:, host:, port: }` or nil
|
|
25
|
+
def self.parse_origin(url)
|
|
26
|
+
uri = URI.parse(url.to_s)
|
|
27
|
+
return nil unless uri.is_a?(URI::HTTP)
|
|
28
|
+
return nil if uri.host.blank?
|
|
29
|
+
|
|
30
|
+
{ scheme: uri.scheme.downcase, host: uri.host.downcase.chomp('.'), port: uri.port }
|
|
31
|
+
rescue URI::InvalidURIError
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns true if the given URL's origin matches this stored origin.
|
|
36
|
+
#
|
|
37
|
+
# Scheme and host must match exactly (host comparison is case- and trailing-dot-
|
|
38
|
+
# insensitive). Port must also match, with the scheme default applied, so storing
|
|
39
|
+
# `https://shop.com` matches `https://shop.com:443`. Loopback/development hosts
|
|
40
|
+
# ({LOOPBACK_HOSTS}) are exempt from the port check, so `http://localhost` still
|
|
41
|
+
# matches `http://localhost:3000`, `:4000`, etc.
|
|
42
|
+
#
|
|
43
|
+
# @param url [String] the candidate URL to check
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def matches?(url)
|
|
46
|
+
candidate = self.class.parse_origin(url)
|
|
47
|
+
allowed = self.class.parse_origin(origin)
|
|
48
|
+
return false if candidate.nil? || allowed.nil?
|
|
49
|
+
return false unless allowed[:scheme] == candidate[:scheme]
|
|
50
|
+
return false unless allowed[:host] == candidate[:host]
|
|
51
|
+
|
|
52
|
+
LOOPBACK_HOSTS.include?(allowed[:host]) || allowed[:port] == candidate[:port]
|
|
53
|
+
end
|
|
54
|
+
|
|
15
55
|
private
|
|
16
56
|
|
|
17
57
|
def origin_must_be_valid_http_url
|
|
@@ -19,12 +59,7 @@ module Spree
|
|
|
19
59
|
|
|
20
60
|
uri = URI.parse(origin)
|
|
21
61
|
|
|
22
|
-
|
|
23
|
-
errors.add(:origin, :invalid)
|
|
24
|
-
return
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
if uri.host.blank?
|
|
62
|
+
if self.class.parse_origin(origin).nil?
|
|
28
63
|
errors.add(:origin, :invalid)
|
|
29
64
|
return
|
|
30
65
|
end
|
data/app/models/spree/api_key.rb
CHANGED
|
@@ -57,6 +57,13 @@ module Spree
|
|
|
57
57
|
validates :store, presence: true
|
|
58
58
|
validates :scopes, presence: true, if: -> { secret? && scopes_enforceable? }
|
|
59
59
|
validate :validate_known_scopes, if: -> { secret? && scopes_enforceable? }
|
|
60
|
+
# Scopes are fixed for the life of the secret — mirroring Stripe/GitHub/AWS:
|
|
61
|
+
# authority is changed by issuing a new key and revoking the old one, never by
|
|
62
|
+
# re-scoping an existing secret in place. This keeps the audit boundary clean
|
|
63
|
+
# and prevents a shared/leaked key from silently gaining write access. The
|
|
64
|
+
# guard is at the model so every entry point (API, legacy admin, rake tasks)
|
|
65
|
+
# is covered uniformly.
|
|
66
|
+
validate :scopes_immutable, if: -> { secret? && persisted? && scopes_changed? }
|
|
60
67
|
|
|
61
68
|
before_validation :generate_token, on: :create
|
|
62
69
|
|
|
@@ -145,6 +152,10 @@ module Spree
|
|
|
145
152
|
errors.add(:scopes, "contains unknown scopes: #{invalid.join(', ')}") if invalid.any?
|
|
146
153
|
end
|
|
147
154
|
|
|
155
|
+
def scopes_immutable
|
|
156
|
+
errors.add(:scopes, Spree.t(:api_key_scopes_immutable))
|
|
157
|
+
end
|
|
158
|
+
|
|
148
159
|
# Generates the token on creation. For publishable keys, stores the raw token
|
|
149
160
|
# in the +token+ column. For secret keys, computes an HMAC-SHA256 digest stored
|
|
150
161
|
# in +token_digest+, saves the first 12 characters as +token_prefix+ for display,
|
|
@@ -22,7 +22,7 @@ module Spree
|
|
|
22
22
|
validates :name, presence: true
|
|
23
23
|
normalizes :name, with: ->(value) { value&.to_s&.squish&.presence }
|
|
24
24
|
|
|
25
|
-
has_many :store_payment_methods, class_name: 'Spree::StorePaymentMethod'
|
|
25
|
+
has_many :store_payment_methods, class_name: 'Spree::StorePaymentMethod', inverse_of: :payment_method
|
|
26
26
|
has_many :stores, class_name: 'Spree::Store', through: :store_payment_methods
|
|
27
27
|
|
|
28
28
|
has_many :payments, class_name: 'Spree::Payment', inverse_of: :payment_method, dependent: :nullify
|
|
@@ -303,8 +303,18 @@ module Spree
|
|
|
303
303
|
to_remove = current - desired
|
|
304
304
|
to_add = desired - current
|
|
305
305
|
|
|
306
|
+
return unless to_remove.any? || to_add.any?
|
|
307
|
+
|
|
306
308
|
remove_products(to_remove) if to_remove.any?
|
|
307
309
|
add_products(to_add) if to_add.any?
|
|
310
|
+
|
|
311
|
+
# Membership is changed via raw `upsert_all`/`delete_all` on `prices`,
|
|
312
|
+
# which bypasses the `variants`/`products` association caches (and the
|
|
313
|
+
# `product_ids` ids-reader memo `current` populated above). Reset them so
|
|
314
|
+
# a same-request read — e.g. the serializer's `product_ids` — reflects the
|
|
315
|
+
# new membership instead of the pre-change set.
|
|
316
|
+
association(:variants).reset
|
|
317
|
+
association(:products).reset
|
|
308
318
|
end
|
|
309
319
|
|
|
310
320
|
def apply_pending_prices
|
|
@@ -96,6 +96,13 @@ module Spree
|
|
|
96
96
|
self.store ||= Spree::Current.store || Spree::Store.default
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
# The store a product's associations (categories, channels) must belong
|
|
100
|
+
# to. Falls back to the request/default store for not-yet-persisted
|
|
101
|
+
# products whose `store` hasn't been assigned.
|
|
102
|
+
def assignable_store
|
|
103
|
+
store || Spree::Current.store || Spree::Store.default
|
|
104
|
+
end
|
|
105
|
+
|
|
99
106
|
def pending_publications?
|
|
100
107
|
@pending_publications_params.present?
|
|
101
108
|
end
|
|
@@ -126,9 +133,14 @@ module Spree
|
|
|
126
133
|
publication.update!(publication_data.slice(:published_at, :unpublished_at))
|
|
127
134
|
publication_ids_in_payload << publication.id
|
|
128
135
|
elsif channel_id.present?
|
|
136
|
+
# Only publish to channels owned by the product's store; ignore
|
|
137
|
+
# ids referencing another store's channel.
|
|
138
|
+
channel = publishable_channels.find_by(id: channel_id)
|
|
139
|
+
next unless channel
|
|
140
|
+
|
|
129
141
|
# Upsert by channel_id so repeat submissions are idempotent
|
|
130
142
|
# against the unique (product_id, channel_id) index.
|
|
131
|
-
publication = product_publications.find_or_initialize_by(channel_id:
|
|
143
|
+
publication = product_publications.find_or_initialize_by(channel_id: channel.id)
|
|
132
144
|
publication.assign_attributes(publication_data.slice(:published_at, :unpublished_at))
|
|
133
145
|
publication.save!
|
|
134
146
|
publication_ids_in_payload << publication.id
|
|
@@ -144,6 +156,11 @@ module Spree
|
|
|
144
156
|
|
|
145
157
|
Spree::PrefixedId.decode_prefixed_id(value) || value
|
|
146
158
|
end
|
|
159
|
+
|
|
160
|
+
# Channels the product may be published to — those owned by its store.
|
|
161
|
+
def publishable_channels
|
|
162
|
+
assignable_store.channels
|
|
163
|
+
end
|
|
147
164
|
end
|
|
148
165
|
end
|
|
149
166
|
end
|
data/app/models/spree/product.rb
CHANGED
|
@@ -211,11 +211,14 @@ module Spree
|
|
|
211
211
|
end
|
|
212
212
|
|
|
213
213
|
# Maps 6.0 API name (category_ids) to model column (taxon_ids).
|
|
214
|
-
# Accepts both prefixed IDs and raw integer IDs.
|
|
214
|
+
# Accepts both prefixed IDs and raw integer IDs. Only taxons belonging to
|
|
215
|
+
# the product's own store are assigned — ids from another store's
|
|
216
|
+
# taxonomies are dropped, preventing cross-store category attachment.
|
|
215
217
|
def category_ids=(ids)
|
|
216
|
-
|
|
218
|
+
decoded_ids = Array(ids).filter_map do |id|
|
|
217
219
|
id.to_s.include?('_') ? Spree::Taxon.decode_prefixed_id(id) : id
|
|
218
220
|
end
|
|
221
|
+
self.taxon_ids = Spree::Taxon.for_store(assignable_store).where(id: decoded_ids).ids
|
|
219
222
|
end
|
|
220
223
|
|
|
221
224
|
# Sync media inline. Entries with `id` patch the existing asset
|
|
@@ -757,6 +760,7 @@ module Spree
|
|
|
757
760
|
def apply_variants(variants_params)
|
|
758
761
|
variant_ids_in_payload = []
|
|
759
762
|
master_touched = false
|
|
763
|
+
mutated = false
|
|
760
764
|
|
|
761
765
|
variants_params.each do |variant_data|
|
|
762
766
|
variant_data = variant_data.to_h.with_indifferent_access
|
|
@@ -767,6 +771,7 @@ module Spree
|
|
|
767
771
|
variant = variants_including_master.find_by_param!(variant_id)
|
|
768
772
|
variant.update!(variant_data)
|
|
769
773
|
variant_ids_in_payload << variant.id
|
|
774
|
+
mutated = true
|
|
770
775
|
elsif options.blank? || (options.is_a?(Array) && options.empty?)
|
|
771
776
|
# An entry with no options addresses the master variant. Building a
|
|
772
777
|
# non-master here would create a phantom duplicate (the auto-built
|
|
@@ -778,18 +783,46 @@ module Spree
|
|
|
778
783
|
target.assign_attributes(variant_data)
|
|
779
784
|
target.save!
|
|
780
785
|
master_touched = true
|
|
786
|
+
mutated = true
|
|
781
787
|
else
|
|
782
788
|
variant = variants.build
|
|
783
789
|
variant.assign_attributes(variant_data)
|
|
784
790
|
variant.save!
|
|
785
791
|
variant_ids_in_payload << variant.id
|
|
792
|
+
mutated = true
|
|
786
793
|
end
|
|
787
794
|
end
|
|
788
795
|
|
|
789
796
|
# Remove variants not in the payload (only non-master). If only the
|
|
790
797
|
# master was touched (simple product), leave existing non-master
|
|
791
798
|
# variants alone — the payload is partial, not a full replacement.
|
|
792
|
-
|
|
799
|
+
if variant_ids_in_payload.any? && !master_touched
|
|
800
|
+
removed = variants.where.not(id: variant_ids_in_payload).destroy_all
|
|
801
|
+
mutated ||= removed.any?
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
sync_variant_state! if mutated
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# Re-syncs the in-memory derived variant state after `apply_variants`
|
|
808
|
+
# mutates variants out-of-band (the variant counter is bumped via
|
|
809
|
+
# `Spree::Product.increment_counter` in a Variant callback, and
|
|
810
|
+
# `default_variant` is memoized). Without this, a freshly built/updated
|
|
811
|
+
# product serialized in the same request returns a stale `variant_count`
|
|
812
|
+
# and a stale `price` (delegated to the memoized `default_variant`).
|
|
813
|
+
#
|
|
814
|
+
# The `default_variant` memo reset here changes shape once master is
|
|
815
|
+
# removed (it becomes a `belongs_to` FK). See
|
|
816
|
+
# docs/plans/6.0-remove-master-variant.md (Phase 3).
|
|
817
|
+
# @return [void]
|
|
818
|
+
def sync_variant_state!
|
|
819
|
+
# `variant_count` is maintained by a direct counter update in a Variant
|
|
820
|
+
# callback, so the in-memory attribute is stale here — re-read it from the
|
|
821
|
+
# row without reloading the whole record.
|
|
822
|
+
self[:variant_count] = self.class.where(id: id).pick(:variant_count)
|
|
823
|
+
variants.reset
|
|
824
|
+
@default_variant = nil
|
|
825
|
+
@default_variant_id = nil
|
|
793
826
|
end
|
|
794
827
|
|
|
795
828
|
def add_associations_from_prototype
|
|
@@ -811,6 +844,7 @@ module Spree
|
|
|
811
844
|
|
|
812
845
|
# Builds variants from a hash of option types & values
|
|
813
846
|
def build_variants_from_option_values_hash
|
|
847
|
+
Spree::Deprecation.warn('Spree::Product#build_variants_from_option_values_hash is deprecated and will be removed in Spree 6.0.')
|
|
814
848
|
ensure_option_types_exist_for_values_hash
|
|
815
849
|
values = option_values_hash.values
|
|
816
850
|
values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
|
|
@@ -925,12 +959,14 @@ module Spree
|
|
|
925
959
|
end
|
|
926
960
|
|
|
927
961
|
def discontinue_on_must_be_later_than_make_active_at
|
|
962
|
+
Spree::Deprecation.warn('Spree::Product#discontinue_on_must_be_later_than_make_active_at is deprecated and will be removed in Spree 6.0.')
|
|
928
963
|
if discontinue_on < make_active_at
|
|
929
964
|
errors.add(:discontinue_on, :invalid_date_range)
|
|
930
965
|
end
|
|
931
966
|
end
|
|
932
967
|
|
|
933
968
|
def requires_price?
|
|
969
|
+
Spree::Deprecation.warn('Spree::Product#requires_price? is deprecated and will be removed in Spree 6.0.')
|
|
934
970
|
Spree::Config[:require_master_price]
|
|
935
971
|
end
|
|
936
972
|
|
|
@@ -86,7 +86,9 @@ module Spree
|
|
|
86
86
|
#
|
|
87
87
|
# Ransack
|
|
88
88
|
#
|
|
89
|
-
|
|
89
|
+
# `name` is whitelisted so the admin global search / command palette can
|
|
90
|
+
# filter via the `name_or_code_cont` predicate without a dedicated scope.
|
|
91
|
+
self.whitelisted_ransackable_attributes = ['name', 'path', 'promotion_category_id', 'code', 'starts_at', 'expires_at']
|
|
90
92
|
self.whitelisted_ransackable_associations = %w[coupon_codes]
|
|
91
93
|
|
|
92
94
|
def self.with_coupon_code(coupon_code)
|
data/app/models/spree/store.rb
CHANGED
|
@@ -82,6 +82,7 @@ module Spree
|
|
|
82
82
|
has_many :variants, through: :products, class_name: 'Spree::Variant', source: :variants_including_master
|
|
83
83
|
has_many :stock_items, through: :variants, class_name: 'Spree::StockItem'
|
|
84
84
|
has_many :prices, through: :variants, class_name: 'Spree::Price'
|
|
85
|
+
has_many :price_lists, class_name: 'Spree::PriceList', inverse_of: :store
|
|
85
86
|
has_many :inventory_units, through: :variants, class_name: 'Spree::InventoryUnit'
|
|
86
87
|
has_many :option_value_variants, through: :variants, class_name: 'Spree::OptionValueVariant'
|
|
87
88
|
has_many :customer_returns, class_name: 'Spree::CustomerReturn', inverse_of: :store
|
|
@@ -278,25 +279,14 @@ module Spree
|
|
|
278
279
|
end
|
|
279
280
|
|
|
280
281
|
# Returns true if the given URL's origin matches one of the store's allowed origins.
|
|
281
|
-
#
|
|
282
|
-
# `http://localhost` will match `http://localhost:3000`, `http://localhost:4000`, etc.
|
|
282
|
+
# See {Spree::AllowedOrigin#matches?} for the matching rules (scheme/host/port).
|
|
283
283
|
#
|
|
284
284
|
# @param url [String] the full URL to check
|
|
285
285
|
# @return [Boolean]
|
|
286
286
|
def allowed_origin?(url)
|
|
287
287
|
return false if url.blank?
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
request_origin = "#{uri.scheme}://#{uri.host}"
|
|
291
|
-
|
|
292
|
-
allowed_origins.pluck(:origin).any? do |stored|
|
|
293
|
-
stored_uri = URI.parse(stored)
|
|
294
|
-
"#{stored_uri.scheme}://#{stored_uri.host}" == request_origin
|
|
295
|
-
rescue URI::InvalidURIError
|
|
296
|
-
false
|
|
297
|
-
end
|
|
298
|
-
rescue URI::InvalidURIError
|
|
299
|
-
false
|
|
289
|
+
allowed_origins.any? { |allowed_origin| allowed_origin.matches?(url) }
|
|
300
290
|
end
|
|
301
291
|
|
|
302
292
|
# Returns the states available for checkout for the store
|
|
@@ -3,7 +3,7 @@ module Spree
|
|
|
3
3
|
self.table_name = 'spree_payment_methods_stores'
|
|
4
4
|
|
|
5
5
|
belongs_to :store, class_name: 'Spree::Store', touch: true
|
|
6
|
-
belongs_to :payment_method, class_name: 'Spree::PaymentMethod', touch: true
|
|
6
|
+
belongs_to :payment_method, class_name: 'Spree::PaymentMethod', touch: true, inverse_of: :store_payment_methods
|
|
7
7
|
|
|
8
8
|
validates :store, :payment_method, presence: true
|
|
9
9
|
validates :store_id, uniqueness: { scope: :payment_method_id }
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -539,7 +539,20 @@ module Spree
|
|
|
539
539
|
# @param compare_at_amount [BigDecimal] the compare at amount to set
|
|
540
540
|
# @return [void]
|
|
541
541
|
def set_price(currency, amount, compare_at_amount = nil)
|
|
542
|
-
|
|
542
|
+
# When the prices association is already loaded (eager-loaded for
|
|
543
|
+
# serialization), reuse the cached base-price instance so readers that
|
|
544
|
+
# branch on `prices.loaded?` (Pricing::Resolver, #price_in, serializers)
|
|
545
|
+
# observe the write without a reload. `base_prices.find_or_initialize_by`
|
|
546
|
+
# would issue a fresh query and return a detached object, leaving the
|
|
547
|
+
# loaded collection — and the serialized response — stale.
|
|
548
|
+
price =
|
|
549
|
+
if prices.loaded?
|
|
550
|
+
prices.detect { |p| p.price_list_id.nil? && p.currency == currency } ||
|
|
551
|
+
prices.build(currency: currency)
|
|
552
|
+
else
|
|
553
|
+
prices.base_prices.find_or_initialize_by(currency: currency)
|
|
554
|
+
end
|
|
555
|
+
|
|
543
556
|
price.amount = amount
|
|
544
557
|
price.compare_at_amount = compare_at_amount
|
|
545
558
|
price.save! if persisted?
|
|
@@ -69,10 +69,7 @@ module Spree
|
|
|
69
69
|
payment_method.name ||= Spree.t(:store_credit_name)
|
|
70
70
|
payment_method.active = true
|
|
71
71
|
|
|
72
|
-
if payment_method.new_record?
|
|
73
|
-
payment_method.stores << store unless payment_method.stores.include?(store)
|
|
74
|
-
payment_method.save!
|
|
75
|
-
end
|
|
72
|
+
payment_method.save! if payment_method.new_record?
|
|
76
73
|
|
|
77
74
|
payment_method
|
|
78
75
|
end
|
|
@@ -99,6 +99,9 @@ module Spree
|
|
|
99
99
|
product.slug = attributes['slug']
|
|
100
100
|
product.sku = attributes['sku'] if attributes['sku'].present? && options.empty?
|
|
101
101
|
product.store = store
|
|
102
|
+
# Publish to the store's default channel so imported products surface
|
|
103
|
+
# in the storefront.
|
|
104
|
+
product.channels << store.default_channel if store.default_channel && product.channels.empty?
|
|
102
105
|
end
|
|
103
106
|
|
|
104
107
|
product.name = attributes['name'] if attributes['name'].present?
|
|
@@ -4,14 +4,17 @@ module Spree
|
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
Spree::Store.all.find_each do |store|
|
|
8
|
+
payment_method = store.payment_methods.find_or_initialize_by(
|
|
9
|
+
type: 'Spree::PaymentMethod::StoreCredit'
|
|
10
|
+
)
|
|
11
|
+
next if payment_method.persisted?
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
payment_method.name = Spree.t(:store_credit_name)
|
|
14
|
+
payment_method.description = Spree.t(:store_credit_name)
|
|
15
|
+
payment_method.active = true
|
|
16
|
+
payment_method.save!
|
|
17
|
+
end
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
end
|
data/config/locales/en.yml
CHANGED
|
@@ -774,6 +774,8 @@ en:
|
|
|
774
774
|
one: "...and %{count} more"
|
|
775
775
|
other: "...and %{count} more"
|
|
776
776
|
api_key: API Key
|
|
777
|
+
api_key_no_current_key: No API key authenticated this request
|
|
778
|
+
api_key_scopes_immutable: cannot be changed after the key is created; create a new key with the scopes you need and revoke this one
|
|
777
779
|
api_keys: API Keys
|
|
778
780
|
apis:
|
|
779
781
|
admin: Admin API
|
|
@@ -1425,6 +1427,7 @@ en:
|
|
|
1425
1427
|
max: Max
|
|
1426
1428
|
max_items: Max Items
|
|
1427
1429
|
max_quantity: Max Quantity
|
|
1430
|
+
me_no_current_user: No admin user authenticated this request
|
|
1428
1431
|
media: Media
|
|
1429
1432
|
memo: Memo
|
|
1430
1433
|
meta_description: Meta Description
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
Spree::Store.all.find_each do |store|
|
|
2
|
+
cc_payment_method = store.payment_methods.find_or_initialize_by(
|
|
3
|
+
type: 'Spree::Gateway::Bogus',
|
|
4
|
+
name: 'Credit Card',
|
|
5
|
+
description: 'Bogus payment gateway.',
|
|
6
|
+
active: true
|
|
7
|
+
)
|
|
8
|
+
cc_payment_method.display_on = 'back_end'
|
|
9
|
+
cc_payment_method.save!
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
check_payment_method.display_on = 'back_end'
|
|
18
|
-
check_payment_method.stores = Spree::Store.all
|
|
19
|
-
check_payment_method.save!
|
|
11
|
+
check_payment_method = store.payment_methods.find_or_initialize_by(
|
|
12
|
+
type: 'Spree::PaymentMethod::Check',
|
|
13
|
+
name: 'Check',
|
|
14
|
+
description: 'Pay by check.',
|
|
15
|
+
active: true
|
|
16
|
+
)
|
|
17
|
+
check_payment_method.display_on = 'back_end'
|
|
18
|
+
check_payment_method.save!
|
|
19
|
+
end
|
data/lib/spree/core/version.rb
CHANGED
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.5.0.
|
|
4
|
+
version: 5.5.0.rc3
|
|
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-06-
|
|
13
|
+
date: 2026-06-18 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: i18n-tasks
|
|
@@ -1816,9 +1816,9 @@ licenses:
|
|
|
1816
1816
|
- BSD-3-Clause
|
|
1817
1817
|
metadata:
|
|
1818
1818
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
1819
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.0.
|
|
1819
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.0.rc3
|
|
1820
1820
|
documentation_uri: https://docs.spreecommerce.org/
|
|
1821
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.5.0.
|
|
1821
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.5.0.rc3
|
|
1822
1822
|
post_install_message:
|
|
1823
1823
|
rdoc_options: []
|
|
1824
1824
|
require_paths:
|