spree_core 5.0.4 → 5.0.6

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +6 -1
  3. data/app/helpers/spree/images_helper.rb +13 -12
  4. data/app/helpers/spree/mail_helper.rb +4 -3
  5. data/app/models/concerns/spree/payment_source_concern.rb +39 -0
  6. data/app/models/spree/credit_card.rb +2 -24
  7. data/app/models/spree/custom_domain.rb +1 -1
  8. data/app/models/spree/gateway/custom_payment_source_method.rb +33 -0
  9. data/app/models/spree/option_value.rb +2 -2
  10. data/app/models/spree/page_sections/featured_posts.rb +4 -0
  11. data/app/models/spree/payment.rb +2 -1
  12. data/app/models/spree/payment_source.rb +21 -0
  13. data/app/models/spree/post.rb +1 -0
  14. data/app/models/spree/product/slugs.rb +112 -0
  15. data/app/models/spree/product.rb +5 -75
  16. data/app/models/spree/promotion/rules/item_total.rb +12 -12
  17. data/app/models/spree/shipping_method.rb +1 -1
  18. data/app/models/spree/taxon.rb +4 -0
  19. data/app/models/spree/theme.rb +1 -1
  20. data/app/models/spree/wishlist.rb +7 -0
  21. data/app/services/spree/products/prepare_nested_attributes.rb +9 -2
  22. data/app/views/spree/addresses/_form.html.erb +1 -1
  23. data/app/views/spree/shared/_payment.html.erb +9 -0
  24. data/config/locales/en.yml +11 -5
  25. data/lib/generators/spree/authentication/devise/devise_generator.rb +5 -2
  26. data/lib/generators/spree/install/install_generator.rb +5 -0
  27. data/lib/spree/core/engine.rb +1 -0
  28. data/lib/spree/core/version.rb +1 -1
  29. data/lib/spree/permitted_attributes.rb +8 -1
  30. data/lib/spree/testing_support/common_rake.rb +25 -7
  31. data/lib/spree/testing_support/extension_rake.rb +1 -1
  32. data/lib/spree/testing_support/factories/page_section_factory.rb +4 -0
  33. data/lib/spree/testing_support/factories/payment_factory.rb +5 -0
  34. data/lib/spree/testing_support/factories/payment_method_factory.rb +5 -0
  35. data/lib/spree/testing_support/factories/payment_source_factory.rb +5 -0
  36. data/lib/spree/testing_support/factories/taxon_factory.rb +6 -0
  37. metadata +8 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8dad7a44435e17f7a0b717356dfe3f8f70cac0e9a18f38f505ded42f3908faf5
4
- data.tar.gz: 109859dd8b1790ab9c0a22c1457e03e81708d79007664f0c8afae841573dd43e
3
+ metadata.gz: 964d9b4646aa2d9c5ac22647f126c304bc7756b3034757b8bda9b0f9faa6d496
4
+ data.tar.gz: '07803df2f7c528d8a35c10b04f02f591b4c2ffe859163c7b2c94e5ad0edfc388'
5
5
  SHA512:
6
- metadata.gz: f09637caac881e203c1ff4f1104c2035c89df6cf671d619b7b9e4786a82a4d65332e6537fa4932545e1782203c82f1b41bb6e4c416674df57de95ccbb014410f
7
- data.tar.gz: 2e499043d9f6393d208ce6cdb823059e734748e79924612d408e6de48d5e36c89cb1799405dd62da06e53aa6b8df3693033a267948fb3e6a4fc2077d4efbce2d
6
+ metadata.gz: 36a5518a9bfd31ed6b6b3a74df7953534a8568cc36a7204acf0eca5499e93bd0b88068f35bd8b7dcbc52d09b9f47ea629cfa582a39d24d717d61c9803e865748
7
+ data.tar.gz: 46fb9aab1ceafb1d44f872e870908e3ff0df44458c4b6248a4bc6e54655f4de4e457b47c9a5c8c133b16cc631f93226a0280585cb8ebee3920b677cd48acee40
@@ -242,7 +242,7 @@ module Spree
242
242
  end
243
243
 
244
244
  def spree_base_cache_key
245
- [
245
+ @spree_base_cache_key ||= [
246
246
  I18n.locale,
247
247
  (current_currency if defined?(current_currency)),
248
248
  defined?(try_spree_current_user) && try_spree_current_user.present?,
@@ -258,6 +258,11 @@ module Spree
258
258
  Spree::DatabaseTypeUtilities.maximum_value_for(:integer)
259
259
  end
260
260
 
261
+ def payment_method_icon_tag(payment_method, opts = {})
262
+ image_tag "payment_icons/#{payment_method}.svg", opts
263
+ rescue Sprockets::Rails::Helper::AssetNotFound
264
+ end
265
+
261
266
  private
262
267
 
263
268
  def create_product_image_tag(image, product, options, style)
@@ -18,10 +18,10 @@ module Spree
18
18
  width = options[:width]
19
19
  height = options[:height]
20
20
 
21
- width = width * 2 if width.present?
22
- height = height * 2 if height.present?
21
+ width *= 2 if width.present?
22
+ height *= 2 if height.present?
23
23
 
24
- return unless image.attached?
24
+ return if image.respond_to?(:attached?) && !image.attached?
25
25
  return unless image.variable?
26
26
 
27
27
  if width.present? && height.present?
@@ -51,17 +51,18 @@ module Spree
51
51
  return unless height
52
52
  return if height.zero?
53
53
 
54
- w, h = width.to_f, height.to_f
54
+ w = width.to_f
55
+ h = height.to_f
55
56
 
56
57
  # Always return width / height, flipping if needed
57
- if h > w
58
- ratio = h / w
59
- elsif h < w
60
- ratio = w / h
61
- else
62
- # h == w, square image
63
- ratio = 1.0
64
- end
58
+ ratio = if h > w
59
+ h / w
60
+ elsif h < w
61
+ w / h
62
+ else
63
+ # h == w, square image
64
+ 1.0
65
+ end
65
66
 
66
67
  ratio.round(3)
67
68
  end
@@ -1,10 +1,11 @@
1
1
  module Spree
2
2
  module MailHelper
3
- include BaseHelper
3
+ include Spree::BaseHelper
4
+ include Spree::ImagesHelper
4
5
 
5
6
  def variant_image_url(variant)
6
- image = default_image_for_product_or_variant(variant)
7
- image ? main_app.cdn_image_url(image.url(:small)) : image_url('noimage/small.png')
7
+ image = variant.default_image
8
+ image.present? && image.attached? ? spree_image_url(image, width: 100, height: 100) : image_url('noimage/small.png')
8
9
  end
9
10
 
10
11
  def name_for(order)
@@ -0,0 +1,39 @@
1
+ module Spree
2
+ module PaymentSourceConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ # Available actions for the payment source.
6
+ # @return [Array<String>]
7
+ def actions
8
+ %w{capture void credit}
9
+ end
10
+
11
+ # Indicates whether its possible to capture the payment
12
+ # @param payment [Spree::Payment]
13
+ # @return [Boolean]
14
+ def can_capture?(payment)
15
+ payment.pending? || payment.checkout?
16
+ end
17
+
18
+ # Indicates whether its possible to void the payment.
19
+ # @param payment [Spree::Payment]
20
+ # @return [Boolean]
21
+ def can_void?(payment)
22
+ !payment.failed? && !payment.void?
23
+ end
24
+
25
+ # Indicates whether its possible to credit the payment. Note that most gateways require that the
26
+ # payment be settled first which generally happens within 12-24 hours of the transaction.
27
+ # @param payment [Spree::Payment]
28
+ # @return [Boolean]
29
+ def can_credit?(payment)
30
+ payment.completed? && payment.credit_allowed > 0
31
+ end
32
+
33
+ # Returns true if the payment source has a payment profile.
34
+ # @return [Boolean]
35
+ def has_payment_profile?
36
+ gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
37
+ end
38
+ end
39
+ end
@@ -2,6 +2,8 @@ module Spree
2
2
  class CreditCard < Spree.base_class
3
3
  include ActiveMerchant::Billing::CreditCardMethods
4
4
  include Spree::Metadata
5
+ include Spree::PaymentSourceConcern
6
+
5
7
  if defined?(Spree::Webhooks::HasWebhooks)
6
8
  include Spree::Webhooks::HasWebhooks
7
9
  end
@@ -135,30 +137,6 @@ module Spree
135
137
  brand.present? ? brand.upcase : Spree.t(:no_cc_type)
136
138
  end
137
139
 
138
- def actions
139
- %w{capture void credit}
140
- end
141
-
142
- # Indicates whether its possible to capture the payment
143
- def can_capture?(payment)
144
- payment.pending? || payment.checkout?
145
- end
146
-
147
- # Indicates whether its possible to void the payment.
148
- def can_void?(payment)
149
- !payment.failed? && !payment.void?
150
- end
151
-
152
- # Indicates whether its possible to credit the payment. Note that most gateways require that the
153
- # payment be settled first which generally happens within 12-24 hours of the transaction.
154
- def can_credit?(payment)
155
- payment.completed? && payment.credit_allowed > 0
156
- end
157
-
158
- def has_payment_profile?
159
- gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
160
- end
161
-
162
140
  # ActiveMerchant needs first_name/last_name because we pass it a Spree::CreditCard and it calls those methods on it.
163
141
  # Looking at the ActiveMerchant source code we should probably be calling #to_active_merchant before passing
164
142
  # the object to ActiveMerchant but this should do for now.
@@ -28,7 +28,7 @@ module Spree
28
28
  def url_is_valid
29
29
  parts = url.split('.')
30
30
 
31
- errors.add(:url, 'use domain or subdomain') if (parts[0] != 'www' && parts.size > 3) || (parts[0] == 'www' && parts.size > 4) || parts.size < 2
31
+ errors.add(:url, 'use domain or subdomain') if parts.size > 4 || parts.size < 2
32
32
  end
33
33
 
34
34
  def ensure_default
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ class Gateway::CustomPaymentSourceMethod < Gateway
3
+ def provider_class
4
+ self.class
5
+ end
6
+
7
+ def payment_source_class
8
+ Spree::PaymentSource
9
+ end
10
+
11
+ def payment_profiles_supported?
12
+ true
13
+ end
14
+
15
+ def show_in_admin?
16
+ false
17
+ end
18
+
19
+ def create_profile(payment)
20
+ return if payment.source.gateway_customer.present?
21
+
22
+ user = payment.source.user || payment.order.user
23
+ return if user.blank?
24
+
25
+ find_or_create_customer(user)
26
+ end
27
+
28
+ # simulate a 3rd party payment gateway api to fetch/or create a customer
29
+ def find_or_create_customer(user)
30
+ gateway_customers.find_or_create_by!(user: user, profile_id: "CUSTOMER-#{user.id}")
31
+ end
32
+ end
33
+ end
@@ -62,9 +62,9 @@ module Spree
62
62
  delegate :name, :presentation, to: :option_type, prefix: true, allow_nil: true
63
63
 
64
64
  def self.to_tom_select_json
65
- all.pluck(:id, :presentation).map do |id, presentation|
65
+ all.pluck(:name, :presentation).map do |name, presentation|
66
66
  {
67
- id: id,
67
+ id: name,
68
68
  name: presentation
69
69
  }
70
70
  end
@@ -19,6 +19,10 @@ module Spree
19
19
  'news'
20
20
  end
21
21
 
22
+ def posts
23
+ Spree::Post.published.by_newest.limit(preferred_max_posts_to_show)
24
+ end
25
+
22
26
  private
23
27
 
24
28
  def make_posts_to_show_valid
@@ -302,7 +302,8 @@ module Spree
302
302
  # Payment profile cannot be created without source
303
303
  return unless source
304
304
  # Imported payments shouldn't create a payment profile.
305
- return if source.imported
305
+ # Imported is only available on Spree::CreditCard, non-credit card payments should not have this attribute.
306
+ return if source.respond_to?(:imported) && source.imported
306
307
 
307
308
  payment_method.create_profile(self)
308
309
  rescue ActiveMerchant::ConnectionError => e
@@ -1,10 +1,31 @@
1
+ # This model is used to store payment sources for non-credit card payments, eg wallet, account, etc.
1
2
  module Spree
2
3
  class PaymentSource < Spree.base_class
3
4
  include Spree::Metadata
5
+ include Spree::PaymentSourceConcern
4
6
 
7
+ #
8
+ # Associations
9
+ #
5
10
  belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
6
11
  belongs_to :user, class_name: Spree.user_class.to_s, optional: true
7
12
 
13
+ #
14
+ # Validations
15
+ #
8
16
  validates_uniqueness_of :gateway_payment_profile_id, scope: :type
17
+
18
+ #
19
+ # Delegations
20
+ #
21
+ delegate :profile_id, to: :gateway_customer, prefix: true, allow_nil: true
22
+
23
+ # Returns the gateway customer for the user.
24
+ # @return [Spree::GatewayCustomer]
25
+ def gateway_customer
26
+ return if user.blank?
27
+
28
+ payment_method.gateway_customers.find_by(user: user)
29
+ end
9
30
  end
10
31
  end
@@ -54,6 +54,7 @@ module Spree
54
54
  # Scopes
55
55
  #
56
56
  scope :published, -> { where(published_at: [..Time.current]) }
57
+ scope :by_newest, -> { order(created_at: :desc) }
57
58
 
58
59
  delegate :name, to: :author, prefix: true, allow_nil: true
59
60
  delegate :title, to: :post_category, prefix: true, allow_nil: true
@@ -0,0 +1,112 @@
1
+ module Spree
2
+ class Product < Spree.base_class
3
+ module Slugs
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend FriendlyId
8
+ include Spree::TranslatableResourceSlug
9
+
10
+ translates :slug
11
+ friendly_id :slug_candidates, use: [:history, :slugged, :scoped, :mobility], scope: spree_base_uniqueness_scope, slug_limit: 255
12
+
13
+ Product::Translation.class_eval do
14
+ acts_as_paranoid
15
+ # deleted translation values also need to be accessible for index views listing deleted resources
16
+ default_scope { unscope(where: :deleted_at) }
17
+
18
+ before_validation :set_slug
19
+ before_validation :ensure_slug_is_unique
20
+
21
+ validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: [*::Spree.base_class.spree_base_uniqueness_scope, :locale] }
22
+
23
+ private
24
+
25
+ def set_slug
26
+ self.slug = generate_slug
27
+ end
28
+
29
+ def generate_slug
30
+ if name.blank? && slug.blank?
31
+ translated_model.name.to_url
32
+ elsif slug.blank?
33
+ name.to_url
34
+ else
35
+ slug.to_url
36
+ end
37
+ end
38
+
39
+ def ensure_slug_is_unique
40
+ slug_exists = self.class.where(slug: slug, locale: locale).where.not(id: id).exists?
41
+ self.slug = [slug, SecureRandom.uuid].join('-') if slug_exists
42
+ end
43
+ end
44
+
45
+ before_validation :downcase_slug
46
+ before_validation :normalize_slug, on: :update
47
+ after_destroy :punch_slugs
48
+ after_restore :regenerate_slug
49
+
50
+ validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
51
+
52
+ def self.slug_available?(slug, id)
53
+ !where(slug: slug).where.not(id: id).exists?
54
+ end
55
+ end
56
+
57
+ def ensure_slug_is_unique(candidate_slug)
58
+ return slug if candidate_slug.blank? || slug.blank?
59
+ return candidate_slug if self.class.slug_available?(candidate_slug, id)
60
+
61
+ normalize_friendly_id([candidate_slug, uuid_for_friendly_id])
62
+ end
63
+
64
+ private
65
+
66
+ def slug_candidates
67
+ if defined?(:deleted_at) && deleted_at.present?
68
+ [
69
+ ['deleted', :name],
70
+ ['deleted', :name, :sku],
71
+ ['deleted', :name, :uuid_for_friendly_id]
72
+ ]
73
+ else
74
+ [
75
+ [:name],
76
+ [:name, :sku],
77
+ [:name, :uuid_for_friendly_id]
78
+ ]
79
+ end
80
+ end
81
+
82
+ def downcase_slug
83
+ slug&.downcase!
84
+ end
85
+
86
+ def normalize_slug
87
+ self.slug = normalize_friendly_id(slug)
88
+ end
89
+
90
+ def regenerate_slug
91
+ self.slug = nil
92
+ save!
93
+ end
94
+
95
+ def punch_slugs
96
+ return if new_record? || frozen?
97
+
98
+ self.slug = nil
99
+
100
+ set_slug
101
+ update_column(:slug, slug)
102
+
103
+ new_slug = ->(rec) { "deleted-#{rec.id}_#{rec.slug}"[..254] }
104
+
105
+ translations.with_deleted.each { |rec| rec.update_columns(slug: new_slug.call(rec)) }
106
+ slugs.with_deleted.each { |rec| rec.update_column(:slug, new_slug.call(rec)) }
107
+
108
+ translations.find_by!(locale: I18n.locale).update_column(:slug, slug) if Spree.use_translations?
109
+ end
110
+ end
111
+ end
112
+ end
@@ -20,14 +20,17 @@
20
20
 
21
21
  module Spree
22
22
  class Product < Spree.base_class
23
- extend FriendlyId
23
+ acts_as_paranoid
24
+ acts_as_taggable_on :tags, :labels
25
+ auto_strip_attributes :name
26
+
24
27
  include Spree::ProductScopes
25
28
  include Spree::MultiStoreResource
26
29
  include Spree::TranslatableResource
27
- include Spree::TranslatableResourceSlug
28
30
  include Spree::MemoizedData
29
31
  include Spree::Metadata
30
32
  include Spree::Product::Webhooks
33
+ include Spree::Product::Slugs
31
34
  if defined?(Spree::VendorConcern)
32
35
  include Spree::VendorConcern
33
36
  end
@@ -53,33 +56,8 @@ module Spree
53
56
  pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' },
54
57
  using: { tsearch: { prefix: true, any_word: true } }
55
58
  end
56
-
57
- before_save :set_slug
58
- acts_as_paranoid
59
- # deleted translation values also need to be accessible for index views listing deleted resources
60
- default_scope { unscope(where: :deleted_at) }
61
- def set_slug
62
- self.slug = generate_slug
63
- end
64
-
65
- private
66
-
67
- def generate_slug
68
- if name.blank? && slug.blank?
69
- translated_model.name.to_url
70
- elsif slug.blank?
71
- name.to_url
72
- else
73
- slug.to_url
74
- end
75
- end
76
59
  end
77
60
 
78
- friendly_id :slug_candidates, use: [:history, :scoped, :mobility], scope: spree_base_uniqueness_scope
79
- acts_as_paranoid
80
- auto_strip_attributes :name
81
- acts_as_taggable_on :tags, :labels
82
-
83
61
  # we need to have this callback before any dependent: :destroy associations
84
62
  # https://github.com/rails/rails/issues/3458
85
63
  before_destroy :ensure_not_in_complete_orders
@@ -145,17 +123,12 @@ module Spree
145
123
  after_initialize :ensure_master
146
124
  after_initialize :assign_default_tax_category
147
125
 
148
- before_validation :downcase_slug
149
- before_validation :normalize_slug, on: :update
150
126
  before_validation :validate_master
151
127
  before_validation :ensure_default_shipping_category
152
128
 
153
129
  after_create :add_associations_from_prototype
154
130
  after_create :build_variants_from_option_values_hash, if: :option_values_hash
155
131
 
156
- after_destroy :punch_slug
157
- after_restore :update_slug_history
158
-
159
132
  after_save :save_master
160
133
  after_save :run_touch_callbacks, if: :anything_changed?
161
134
  after_save :reset_nested_changes
@@ -173,7 +146,6 @@ module Spree
173
146
  validates :price, if: :requires_price?
174
147
  end
175
148
 
176
- validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
177
149
  validate :discontinue_on_must_be_later_than_make_active_at, if: -> { make_active_at && discontinue_on }
178
150
 
179
151
  scope :for_store, ->(store) { joins(:store_products).where(StoreProduct.table_name => { store_id: store.id }) }
@@ -512,17 +484,6 @@ module Spree
512
484
  where conditions.inject(:or)
513
485
  end
514
486
 
515
- def self.slug_available?(slug, id)
516
- !where(slug: slug).where.not(id: id).exists?
517
- end
518
-
519
- def ensure_slug_is_unique(candidate_slug)
520
- return slug if candidate_slug.blank? || slug.blank?
521
- return candidate_slug if self.class.slug_available?(candidate_slug, id)
522
-
523
- normalize_friendly_id([candidate_slug, uuid_for_friendly_id])
524
- end
525
-
526
487
  # Suitable for displaying only variants that has at least one option value.
527
488
  # There may be scenarios where an option type is removed and along with it
528
489
  # all option values. At that point all variants associated with only those
@@ -738,25 +699,6 @@ module Spree
738
699
  self.tax_category = Spree::TaxCategory.default if new_record?
739
700
  end
740
701
 
741
- def normalize_slug
742
- self.slug = normalize_friendly_id(slug)
743
- end
744
-
745
- def punch_slug
746
- # punch slug with date prefix to allow reuse of original
747
- return if frozen?
748
-
749
- update_column(:slug, "#{Time.current.to_i}_#{slug}"[0..254])
750
-
751
- translations.with_deleted.each do |t|
752
- t.update_column :slug, "#{Time.current.to_i}_#{t.slug}"[0..254]
753
- end
754
- end
755
-
756
- def update_slug_history
757
- save!
758
- end
759
-
760
702
  def anything_changed?
761
703
  saved_changes? || @nested_changes
762
704
  end
@@ -812,14 +754,6 @@ module Spree
812
754
  end
813
755
  end
814
756
 
815
- # Try building a slug based on the following fields in increasing order of specificity.
816
- def slug_candidates
817
- [
818
- :name,
819
- [:name, :sku]
820
- ]
821
- end
822
-
823
757
  def run_touch_callbacks
824
758
  run_callbacks(:touch)
825
759
  end
@@ -867,10 +801,6 @@ module Spree
867
801
  previously_new_record? || tag_list_previously_changed? || available_on_previously_changed?
868
802
  end
869
803
 
870
- def downcase_slug
871
- slug&.downcase!
872
- end
873
-
874
804
  def after_activate
875
805
  # Implement your logic here
876
806
  end
@@ -27,39 +27,39 @@ module Spree
27
27
  upper_limit_condition = true
28
28
  end
29
29
 
30
- eligibility_errors.add(:base, ineligible_message_max) unless upper_limit_condition
31
- eligibility_errors.add(:base, ineligible_message_min) unless lower_limit_condition
30
+ eligibility_errors.add(:base, ineligible_message_max(order)) unless upper_limit_condition
31
+ eligibility_errors.add(:base, ineligible_message_min(order)) unless lower_limit_condition
32
32
 
33
33
  eligibility_errors.empty?
34
34
  end
35
35
 
36
36
  private
37
37
 
38
- def formatted_amount_min
39
- Spree::Money.new(preferred_amount_min).to_s
38
+ def formatted_amount_min(order)
39
+ Spree::Money.new(preferred_amount_min, currency: order.currency).to_s
40
40
  end
41
41
 
42
- def formatted_amount_max
42
+ def formatted_amount_max(order)
43
43
  if preferred_amount_max.present?
44
- Spree::Money.new(preferred_amount_max).to_s
44
+ Spree::Money.new(preferred_amount_max, currency: order.currency).to_s
45
45
  else
46
46
  Spree.t('no_maximum')
47
47
  end
48
48
  end
49
49
 
50
- def ineligible_message_max
50
+ def ineligible_message_max(order)
51
51
  if preferred_operator_max == 'lt'
52
- eligibility_error_message(:item_total_more_than_or_equal, amount: formatted_amount_max)
52
+ eligibility_error_message(:item_total_more_than_or_equal, amount: formatted_amount_max(order))
53
53
  else
54
- eligibility_error_message(:item_total_more_than, amount: formatted_amount_max)
54
+ eligibility_error_message(:item_total_more_than, amount: formatted_amount_max(order))
55
55
  end
56
56
  end
57
57
 
58
- def ineligible_message_min
58
+ def ineligible_message_min(order)
59
59
  if preferred_operator_min == 'gte'
60
- eligibility_error_message(:item_total_less_than, amount: formatted_amount_min)
60
+ eligibility_error_message(:item_total_less_than, amount: formatted_amount_min(order))
61
61
  else
62
- eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount_min)
62
+ eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount_min(order))
63
63
  end
64
64
  end
65
65
  end
@@ -93,7 +93,7 @@ module Spree
93
93
  if estimated_transit_business_days_min == estimated_transit_business_days_max
94
94
  estimated_transit_business_days_min.to_s
95
95
  else
96
- "#{estimated_transit_business_days_min}-#{estimated_transit_business_days_max}"
96
+ [estimated_transit_business_days_min, estimated_transit_business_days_max].compact.join("-")
97
97
  end
98
98
  end
99
99
 
@@ -153,6 +153,10 @@ module Spree
153
153
  sort_order == 'manual'
154
154
  end
155
155
 
156
+ def page_builder_image
157
+ square_image.presence || image
158
+ end
159
+
156
160
  def active_products_with_descendants
157
161
  @active_products_with_descendants ||= store.products.
158
162
  joins(:classifications).
@@ -155,7 +155,7 @@ module Spree
155
155
  end
156
156
  end
157
157
 
158
- # Returns an array of available layout section classes for the theme
158
+ # Returns an array of available layout section classes for the theme, eg. header, footer, newsletter, etc.
159
159
  #
160
160
  # @return [Array<Class>]
161
161
  def available_layout_sections
@@ -36,6 +36,13 @@ module Spree
36
36
  @wished_items_count ||= variant_ids.count
37
37
  end
38
38
 
39
+ # returns the variant ids in the wishlist
40
+ #
41
+ # @return [Array<Integer>]
42
+ def variant_ids
43
+ @variant_ids ||= wished_items.pluck(:variant_id)
44
+ end
45
+
39
46
  def self.get_by_param(param)
40
47
  find_by(token: param)
41
48
  end
@@ -136,8 +136,15 @@ module Spree
136
136
  o.position = opt[:position]
137
137
  o.save!
138
138
  end
139
- option_value = option_type.option_values.where(name: opt[:value].parameterize).first_or_initialize do |o|
140
- o.name = o.presentation = opt[:value]
139
+
140
+ option_value_identificator = if opt[:option_value_name].present?
141
+ opt[:option_value_name]
142
+ else
143
+ opt[:option_value_presentation].parameterize
144
+ end
145
+
146
+ option_value = option_type.option_values.where(name: option_value_identificator).first_or_initialize do |o|
147
+ o.name = o.presentation = opt[:option_value_presentation]
141
148
  o.save!
142
149
  end
143
150
 
@@ -5,7 +5,7 @@
5
5
  data-controller="address-form address-autocomplete"
6
6
  data-address-form-countries-value="<%= available_countries.to_json %>"
7
7
  data-address-form-states-value="<%= available_states.to_json %>"
8
- data-address-form-current-state-id-value="<%= address.state_id %>"
8
+ data-address-form-current-state-id-value="<%= address.state_id || address_form.object.state_id %>"
9
9
  >
10
10
  <div id="<%= "#{address_id}country" %>">
11
11
  <div class="form-group mb-0">
@@ -30,6 +30,15 @@
30
30
  <div class="flex-shrink-0">
31
31
  <%= payment_method_icon_tag source.class.to_s.demodulize.downcase, height: 30 %>
32
32
  </div>
33
+ <div>
34
+ <% if payment.payment_method.respond_to?(:source_partial_name) %>
35
+ <%= render partial: "spree/payment_sources/#{payment.payment_method.source_partial_name}", locals: { payment: payment, source: source } %>
36
+ <% elsif source.respond_to?(:name) %>
37
+ <%= source.name %>
38
+ <% else %>
39
+ <%= source.class.to_s.demodulize.downcase %>
40
+ <% end %>
41
+ </div>
33
42
  <% else %>
34
43
  <div class="flex-shrink-0">
35
44
  <%= payment_method_icon_tag payment.payment_method.payment_icon_name %>
@@ -716,6 +716,7 @@ en:
716
716
  capture: Capture
717
717
  capture_events: Capture events
718
718
  card_code: Card Verification Code (CVC)
719
+ card_expiration_placeholder: MM/YYYY
719
720
  card_number: Card Number
720
721
  card_type: Brand
721
722
  card_type_is: Card type is
@@ -891,6 +892,8 @@ en:
891
892
  destroy: Destroy
892
893
  details: Details
893
894
  developers: Developers
895
+ didn_t_receive_confirmation_instructions: Didn't receive confirmation instructions?
896
+ didn_t_receive_unlock_instructions: Didn't receive unlock instructions?
894
897
  digital:
895
898
  digital_delivery: Digital Delivery
896
899
  digital_assets: Digital assets
@@ -1208,6 +1211,9 @@ en:
1208
1211
  metadata: Metadata
1209
1212
  min: Min
1210
1213
  minimal_amount: Minimal Amount
1214
+ minimum_password_length:
1215
+ one: "(%{count} character minimum)"
1216
+ other: "(%{count} characters minimum)"
1211
1217
  missing_return_authorization: Missing Return Authorization for %{item_name}.
1212
1218
  month: Month
1213
1219
  more: More
@@ -1481,7 +1487,7 @@ en:
1481
1487
  password_protected: Password protected
1482
1488
  paste: Paste
1483
1489
  path: Path
1484
- pay: pay
1490
+ pay: Pay
1485
1491
  payment: Payment
1486
1492
  payment_amount: Payment amount
1487
1493
  payment_attempts: failed attempts
@@ -1498,11 +1504,11 @@ en:
1498
1504
  payment_source: Payment source
1499
1505
  payment_state: Payment State
1500
1506
  payment_states:
1501
- balance_due: Balance due
1507
+ balance_due: Balance Due
1502
1508
  checkout: Checkout
1503
1509
  complete: Complete
1504
1510
  completed: Completed
1505
- credit_owed: Credit owed
1511
+ credit_owed: Credit Owed
1506
1512
  failed: Failed
1507
1513
  paid: Paid
1508
1514
  partially_refunded: Partially Refunded
@@ -1526,8 +1532,6 @@ en:
1526
1532
  please_check_back_soon: Please check back soon.
1527
1533
  please_define_payment_methods: Please define some payment methods first.
1528
1534
  please_enter_reasonable_quantity: Please enter a reasonable quantity.
1529
- please_select: Please select
1530
- please_select_all_options: Please select all options
1531
1535
  policies: Policies
1532
1536
  populate_get_error: Something went wrong. Please try adding the item again.
1533
1537
  post_categories: Post categories
@@ -1878,6 +1882,7 @@ en:
1878
1882
  show_store_selector:
1879
1883
  long: Display the Store selector in the main nav bar of Storefront and allow users to change Store and Currency
1880
1884
  short: Show Store selector
1885
+ sign_in_with_provider: Sign in with %{provider}
1881
1886
  sign_out: Sign out
1882
1887
  sign_up: Sign Up
1883
1888
  site_name: Site name
@@ -2098,6 +2103,7 @@ en:
2098
2103
  timezone: Timezone
2099
2104
  title: Title
2100
2105
  title_link: Title link
2106
+ to: To
2101
2107
  to_add_variants_you_must_first_define: To add variants, you must first define
2102
2108
  toggle_menu: Toggle menu
2103
2109
  top_suggestions: Top suggestions
@@ -33,6 +33,8 @@ module Spree
33
33
  include Spree::UserPaymentSource
34
34
  RUBY
35
35
  end
36
+ gsub_file user_class_file, "< ApplicationRecord", "< Spree.base_class"
37
+
36
38
  say "Successfully added Spree user modules into #{user_class_file}"
37
39
  else
38
40
  say "Could not locate user model file at #{user_class_file}. Please add these lines manually:", :red
@@ -42,13 +44,14 @@ module Spree
42
44
  include Spree::UserMethods
43
45
  include Spree::UserPaymentSource
44
46
  RUBY
47
+
48
+ say "Please replace < ApplicationRecord with < Spree.base_class in #{user_class_file}"
45
49
  end
46
50
 
47
51
  append_file 'config/initializers/spree.rb' do
48
52
  %Q{
49
53
  if defined?(Devise) && Devise.respond_to?(:parent_controller)
50
- Devise.parent_controller = "Spree::StoreController"
51
- Devise.parent_mailer = "Spree::BaseMailer"
54
+ Devise.parent_controller = "Spree::BaseController"
52
55
  end\n}
53
56
  end
54
57
  end
@@ -78,6 +78,11 @@ module Spree
78
78
  def install_storefront
79
79
  if @install_storefront && Spree::Core::Engine.frontend_available?
80
80
  generate 'spree:storefront:install'
81
+
82
+ # generate devise controllers if authentication is devise
83
+ if @authentication == 'devise'
84
+ generate 'spree:storefront:devise'
85
+ end
81
86
  end
82
87
  end
83
88
 
@@ -96,6 +96,7 @@ module Spree
96
96
  Rails.application.config.spree.payment_methods = [
97
97
  Spree::Gateway::Bogus,
98
98
  Spree::Gateway::BogusSimple,
99
+ Spree::Gateway::CustomPaymentSourceMethod,
99
100
  Spree::PaymentMethod::Check,
100
101
  Spree::PaymentMethod::StoreCredit
101
102
  ]
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.0.4'.freeze
2
+ VERSION = '5.0.6'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -165,7 +165,14 @@ module Spree
165
165
  :product_id, :product, :option_values_attributes, :price, :compare_at_price,
166
166
  :weight, :height, :width, :depth, :sku, :barcode, :cost_currency,
167
167
  :weight_unit, :dimensions_unit,
168
- { options: [:name, :value], option_value_ids: [] }
168
+ {
169
+ options: [:id, :name, :option_value_presentation, :option_value_name, :position, :_destroy],
170
+ stock_items_attributes: [:id, :count_on_hand, :stock_location_id, :backorderable, :_destroy],
171
+ prices_attributes: [:id, :amount, :compare_at_amount, :currency, :_destroy],
172
+ price: {},
173
+ option_value_variants_attributes: [:id, :option_value_id, :_destroy],
174
+ option_value_ids: []
175
+ }
169
176
  ]
170
177
 
171
178
  @@wishlist_attributes = [:name, :is_default, :is_private]
@@ -46,21 +46,39 @@ namespace :common do
46
46
  "--authentication=#{args[:authentication]}"
47
47
  ]
48
48
 
49
- puts 'Setting up dummy database...'
50
- system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
51
- system('bundle exec rake db:drop db:create > /dev/null 2>&1')
52
- Spree::DummyModelGenerator.start
53
- system('bundle exec rake db:migrate > /dev/null 2>&1')
49
+ if !skip_javascript || ENV['LIB_NAME'] == 'spree/emails'
50
+ puts 'Precompiling assets...'
51
+ system('bundle exec rake assets:precompile > /dev/null 2>&1')
52
+ end
53
+
54
+ unless ENV['NO_MIGRATE']
55
+ puts 'Setting up dummy database...'
56
+ system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
57
+ system('bundle exec rake db:drop db:create > /dev/null 2>&1')
58
+ Spree::DummyModelGenerator.start
59
+ system('bundle exec rake db:migrate > /dev/null 2>&1')
60
+ end
54
61
 
55
62
  begin
56
63
  require "generators/#{ENV['LIB_NAME']}/install/install_generator"
57
64
  puts 'Running extension installation generator...'
58
- "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start(['--auto-run-migrations'])
65
+
66
+ if ENV['NO_MIGRATE']
67
+ "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start([])
68
+ else
69
+ "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start(['--auto-run-migrations'])
70
+ end
59
71
  rescue LoadError
60
72
  puts 'Skipping installation no generator to run...'
61
73
  end
74
+ end
62
75
 
63
- system('bundle exec rake assets:precompile > /dev/null 2>&1') if !skip_javascript || ENV['LIB_NAME'] == 'spree/emails'
76
+ task :db_setup do |_t|
77
+ puts 'Setting up dummy database...'
78
+ system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
79
+ system('bundle exec rake db:drop db:create > /dev/null 2>&1')
80
+ Spree::DummyModelGenerator.start
81
+ system('bundle exec rake db:migrate > /dev/null 2>&1')
64
82
  end
65
83
 
66
84
  task :seed do |_t|
@@ -2,7 +2,7 @@ require 'spree/testing_support/common_rake'
2
2
 
3
3
  desc 'Generates a dummy app for testing an extension'
4
4
  namespace :extension do
5
- task :test_app, [:user_class] do |_t, args|
5
+ task :test_app, [:authentication, :user_class] do |_t, args|
6
6
  Spree::DummyGeneratorHelper.inject_extension_requirements = true
7
7
  Rake::Task['common:test_app'].execute(args.with_defaults(install_admin: true, install_storefront: true))
8
8
  end
@@ -20,5 +20,9 @@ FactoryBot.define do
20
20
  factory :newsletter_page_section, class: Spree::PageSections::Newsletter
21
21
 
22
22
  factory :video_page_section, class: Spree::PageSections::Video
23
+
24
+ factory :image_with_text_page_section, class: Spree::PageSections::ImageWithText
25
+
26
+ factory :featured_posts_page_section, class: Spree::PageSections::FeaturedPosts
23
27
  end
24
28
  end
@@ -14,6 +14,11 @@ FactoryBot.define do
14
14
  create(:refund, amount: 5, payment: payment)
15
15
  end
16
16
  end
17
+
18
+ factory :custom_payment, class: Spree::Payment do
19
+ payment_method { create(:custom_payment_method, stores: [order.store]) }
20
+ source { create(:payment_source, user: order.user, payment_method: payment_method) }
21
+ end
17
22
  end
18
23
 
19
24
  factory :check_payment, class: Spree::Payment do
@@ -37,4 +37,9 @@ FactoryBot.define do
37
37
  active { true }
38
38
  auto_capture { true }
39
39
  end
40
+
41
+ factory :custom_payment_method, parent: :payment_method, class: Spree::Gateway::CustomPaymentSourceMethod do
42
+ type { 'Spree::Gateway::CustomPaymentSourceMethod' }
43
+ name { 'Custom' }
44
+ end
40
45
  end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :payment_source, class: Spree::PaymentSource do
3
+ association(:payment_method, factory: :custom_payment_method)
4
+ end
5
+ end
@@ -9,6 +9,12 @@ FactoryBot.define do
9
9
  trait :with_description do
10
10
  description { '<div>Test <strong>description</strong></div>' }
11
11
  end
12
+
13
+ trait :with_header_image do
14
+ after(:create) do |taxon|
15
+ taxon.image.attach(io: File.new(Spree::Core::Engine.root.join('spec', 'fixtures', 'thinking-cat.jpg')), filename: 'thinking-cat.jpg')
16
+ end
17
+ end
12
18
  end
13
19
 
14
20
  factory :automatic_taxon, parent: :taxon do
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.0.4
4
+ version: 5.0.6
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: 2025-05-22 00:00:00.000000000 Z
13
+ date: 2025-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -603,6 +603,7 @@ files:
603
603
  - app/models/concerns/spree/number_as_param.rb
604
604
  - app/models/concerns/spree/number_identifier.rb
605
605
  - app/models/concerns/spree/parameterizable_name.rb
606
+ - app/models/concerns/spree/payment_source_concern.rb
606
607
  - app/models/concerns/spree/previewable.rb
607
608
  - app/models/concerns/spree/product_scopes.rb
608
609
  - app/models/concerns/spree/ransackable_attributes.rb
@@ -666,6 +667,7 @@ files:
666
667
  - app/models/spree/gateway.rb
667
668
  - app/models/spree/gateway/bogus.rb
668
669
  - app/models/spree/gateway/bogus_simple.rb
670
+ - app/models/spree/gateway/custom_payment_source_method.rb
669
671
  - app/models/spree/gateway_customer.rb
670
672
  - app/models/spree/image.rb
671
673
  - app/models/spree/image/configuration/active_storage.rb
@@ -764,6 +766,7 @@ files:
764
766
  - app/models/spree/preference.rb
765
767
  - app/models/spree/price.rb
766
768
  - app/models/spree/product.rb
769
+ - app/models/spree/product/slugs.rb
767
770
  - app/models/spree/product/webhooks.rb
768
771
  - app/models/spree/product_option_type.rb
769
772
  - app/models/spree/product_promotion_rule.rb
@@ -1229,6 +1232,7 @@ files:
1229
1232
  - lib/spree/testing_support/factories/payment_capture_event_factory.rb
1230
1233
  - lib/spree/testing_support/factories/payment_factory.rb
1231
1234
  - lib/spree/testing_support/factories/payment_method_factory.rb
1235
+ - lib/spree/testing_support/factories/payment_source_factory.rb
1232
1236
  - lib/spree/testing_support/factories/post_category_factory.rb
1233
1237
  - lib/spree/testing_support/factories/post_factory.rb
1234
1238
  - lib/spree/testing_support/factories/price_factory.rb
@@ -1308,9 +1312,9 @@ licenses:
1308
1312
  - BSD-3-Clause
1309
1313
  metadata:
1310
1314
  bug_tracker_uri: https://github.com/spree/spree/issues
1311
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.0.4
1315
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.0.6
1312
1316
  documentation_uri: https://docs.spreecommerce.org/
1313
- source_code_uri: https://github.com/spree/spree/tree/v5.0.4
1317
+ source_code_uri: https://github.com/spree/spree/tree/v5.0.6
1314
1318
  post_install_message:
1315
1319
  rdoc_options: []
1316
1320
  require_paths: