spree_core 4.8.3 → 4.9.0

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/line_items/find_by_variant.rb +4 -2
  3. data/app/finders/spree/products/find.rb +1 -1
  4. data/app/finders/spree/variants/find.rb +30 -0
  5. data/app/models/spree/address.rb +12 -2
  6. data/app/models/spree/adjustment.rb +5 -0
  7. data/app/models/spree/asset.rb +8 -0
  8. data/app/models/spree/calculator/flat_rate.rb +3 -0
  9. data/app/models/spree/calculator/flexi_rate.rb +3 -0
  10. data/app/models/spree/calculator/percent_on_line_item.rb +3 -0
  11. data/app/models/spree/calculator/shipping/flat_rate.rb +13 -1
  12. data/app/models/spree/cms_section_image.rb +0 -6
  13. data/app/models/spree/customer_return.rb +1 -0
  14. data/app/models/spree/icon.rb +0 -6
  15. data/app/models/spree/image/configuration/active_storage.rb +0 -8
  16. data/app/models/spree/image.rb +17 -0
  17. data/app/models/spree/line_item.rb +23 -6
  18. data/app/models/spree/option_value_variant.rb +4 -0
  19. data/app/models/spree/order/currency_updater.rb +2 -2
  20. data/app/models/spree/order/webhooks.rb +19 -0
  21. data/app/models/spree/order.rb +17 -6
  22. data/app/models/spree/order_merger.rb +6 -0
  23. data/app/models/spree/payment/gateway_options.rb +4 -0
  24. data/app/models/spree/payment/webhooks.rb +15 -0
  25. data/app/models/spree/payment.rb +7 -7
  26. data/app/models/spree/price.rb +45 -0
  27. data/app/models/spree/product/webhooks.rb +17 -0
  28. data/app/models/spree/product.rb +7 -9
  29. data/app/models/spree/promotion/actions/free_shipping.rb +13 -0
  30. data/app/models/spree/promotion/rules/first_order.rb +6 -1
  31. data/app/models/spree/promotion.rb +2 -2
  32. data/app/models/spree/promotion_handler/coupon.rb +3 -2
  33. data/app/models/spree/refund.rb +14 -2
  34. data/app/models/spree/reimbursement/emails.rb +11 -0
  35. data/app/models/spree/reimbursement.rb +1 -6
  36. data/app/models/spree/role_user.rb +1 -1
  37. data/app/models/spree/shipment/emails.rb +11 -0
  38. data/app/models/spree/shipment/webhooks.rb +11 -0
  39. data/app/models/spree/shipment.rb +58 -7
  40. data/app/models/spree/shipment_handler.rb +2 -6
  41. data/app/models/spree/shipping_method.rb +6 -0
  42. data/app/models/spree/shipping_method_zone.rb +4 -2
  43. data/app/models/spree/shipping_rate.rb +1 -1
  44. data/app/models/spree/stock/package.rb +4 -0
  45. data/app/models/spree/stock/quantifier.rb +22 -4
  46. data/app/models/spree/stock_item/webhooks.rb +6 -0
  47. data/app/models/spree/stock_item.rb +1 -3
  48. data/app/models/spree/stock_location.rb +15 -0
  49. data/app/models/spree/stock_movement/webhooks.rb +6 -0
  50. data/app/models/spree/stock_movement.rb +1 -3
  51. data/app/models/spree/store.rb +1 -5
  52. data/app/models/spree/store_credit.rb +11 -12
  53. data/app/models/spree/store_favicon_image.rb +0 -6
  54. data/app/models/spree/store_logo.rb +0 -5
  55. data/app/models/spree/store_mailer_logo.rb +0 -6
  56. data/app/models/spree/tax_rate.rb +3 -1
  57. data/app/models/spree/taxon_image/configuration/active_storage.rb +0 -6
  58. data/app/models/spree/variant/webhooks.rb +6 -0
  59. data/app/models/spree/variant.rb +21 -3
  60. data/app/models/spree/wishlist.rb +9 -0
  61. data/app/presenters/spree/variants/options_presenter.rb +34 -2
  62. data/app/services/spree/cart/add_item.rb +2 -0
  63. data/app/services/spree/cart/destroy.rb +14 -4
  64. data/app/services/spree/seeds/all.rb +3 -0
  65. data/app/services/spree/seeds/payment_methods.rb +18 -0
  66. data/app/services/spree/seeds/stock_locations.rb +1 -1
  67. data/app/services/spree/seeds/zones.rb +19 -19
  68. data/config/locales/en.yml +2 -0
  69. data/db/migrate/20240623172111_add_deleted_at_to_spree_stock_locations.rb +6 -0
  70. data/db/migrate/20240725124530_add_refunder_to_spree_refunds.rb +6 -0
  71. data/lib/spree/core/controller_helpers/order.rb +5 -5
  72. data/lib/spree/core/dependencies.rb +2 -1
  73. data/lib/spree/core/preferences/preferable.rb +4 -2
  74. data/lib/spree/core/preferences/preferable_class_methods.rb +3 -2
  75. data/lib/spree/core/version.rb +1 -1
  76. data/lib/spree/core/webhooks.rb +15 -7
  77. data/lib/spree/core.rb +1 -0
  78. data/lib/spree/money.rb +1 -1
  79. data/lib/spree/testing_support/authorization_helpers.rb +2 -2
  80. data/lib/spree/testing_support/common_rake.rb +1 -1
  81. data/spree_core.gemspec +1 -0
  82. metadata +31 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ee4d4cee87ddfaa3a840606e85a256ec3751fd79f509e481fc08557c908f0a5
4
- data.tar.gz: ef15433c0b5ac981d367bf1ee209d5ff126f9930e986bc824b74a554cb56e171
3
+ metadata.gz: 3966d98b01b3e3ac7e23384c37933bfd0cd961d05a112b6b538e3973bf62e0df
4
+ data.tar.gz: d1305f1376e401557ae786799c862ab927c4aa26986cb444bdd8db4230d92cd8
5
5
  SHA512:
6
- metadata.gz: 563286ea01a9de90ed7d4fa10398715511835028bf0ec274a69ea482841793bab3cb7441778b574e2a4add3c128b38b1bb65c31a0e1b1e669361244fbf756b11
7
- data.tar.gz: a0fa015209ed89a6c2d69cef0bc17e01d2eb7002b869b0fccff2aab8b0ede4f443a6933d15d0832589b5eeae91b2b80364f625af36b6d3a0e50a641c5a85b1f6
6
+ metadata.gz: 768f2fb6195ca9528398233360ab1f4b7be974e7fa5999ba32a5aa53b89d52bcce93ef252dd45ba7bd627ea329e5ebb9e11200dea6fecdb494c45078b2a97e88
7
+ data.tar.gz: 1a3c031451d48a9fd021eb0529e942db6adcd732a9e558c7fdefd7574c749192982cece0854678a198489512d421e22aa466bdd958f240d8bc5af23a4de9d240
@@ -2,11 +2,13 @@ module Spree
2
2
  module LineItems
3
3
  class FindByVariant
4
4
  def execute(order:, variant:, options: {})
5
- order.line_items.detect do |line_item|
6
- next unless line_item.variant_id == variant.id
5
+ line_item = order.line_items.loaded? ? order.line_items.detect { |li| li.variant_id == variant.id } : order.line_items.find_by(variant_id: variant.id)
7
6
 
7
+ if line_item
8
8
  Spree::Dependencies.cart_compare_line_items_service.constantize.call(order: order, line_item: line_item, options: options).value
9
9
  end
10
+
11
+ line_item
10
12
  end
11
13
  end
12
14
  end
@@ -263,7 +263,7 @@ module Spree
263
263
  def taxon_ids(taxons_ids)
264
264
  return if taxons_ids.nil? || taxons_ids.to_s.blank?
265
265
 
266
- taxons = store.taxons.where(id: taxons_ids.to_s.split(','))
266
+ taxons = Spree::Taxon.for_store(store).where(id: taxons_ids.to_s.split(','))
267
267
  taxons.map(&:cached_self_and_descendants_ids).flatten.compact.uniq.map(&:to_s)
268
268
  end
269
269
 
@@ -0,0 +1,30 @@
1
+ module Spree
2
+ module Variants
3
+ class Find
4
+ def initialize(scope:, params:)
5
+ @scope = scope
6
+ @options = params.dig(:filter, :options).try(:to_unsafe_hash)
7
+ end
8
+
9
+ def execute
10
+ variants = by_options(scope)
11
+ variants.distinct
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :scope, :options
17
+
18
+ def options?
19
+ options.present?
20
+ end
21
+
22
+ def by_options(variants)
23
+ return variants unless options?
24
+
25
+ variants_ids = options.map { |key, value| variants.with_option_value(key, value)&.ids }.compact.uniq
26
+ variants.where(id: variants_ids.reduce(&:intersection))
27
+ end
28
+ end
29
+ end
30
+ end
@@ -46,7 +46,9 @@ module Spree
46
46
  before_validation :clear_invalid_state_entities, if: -> { country.present? }, on: :update
47
47
 
48
48
  with_options presence: true do
49
- validates :firstname, :lastname, :address1, :city, :country
49
+ validates :firstname, :lastname, if: :require_name?
50
+ validates :address1, if: :require_street?
51
+ validates :city, :country
50
52
  validates :zipcode, if: :require_zipcode?
51
53
  validates :phone, if: :require_phone?
52
54
  end
@@ -137,12 +139,20 @@ module Spree
137
139
  country ? country.zipcode_required? : true
138
140
  end
139
141
 
142
+ def require_name?
143
+ true
144
+ end
145
+
146
+ def require_street?
147
+ true
148
+ end
149
+
140
150
  def editable?
141
151
  new_record? || (shipments.empty? && !Order.complete.where('bill_address_id = ? OR ship_address_id = ?', id, id).exists?)
142
152
  end
143
153
 
144
154
  def can_be_deleted?
145
- shipments.empty? && !Order.where('bill_address_id = ? OR ship_address_id = ?', id, id).exists?
155
+ shipments.empty? && !Order.complete.where('bill_address_id = ? OR ship_address_id = ?', id, id).exists?
146
156
  end
147
157
 
148
158
  def check
@@ -64,6 +64,7 @@ module Spree
64
64
  scope :charge, -> { where("#{quoted_table_name}.amount >= 0") }
65
65
  scope :credit, -> { where("#{quoted_table_name}.amount < 0") }
66
66
  scope :nonzero, -> { where("#{quoted_table_name}.amount != 0") }
67
+ scope :non_zero, -> { where.not(amount: [nil, 0]) }
67
68
  scope :promotion, -> { where(source_type: 'Spree::PromotionAction') }
68
69
  scope :return_authorization, -> { where(source_type: 'Spree::ReturnAuthorization') }
69
70
  scope :is_included, -> { where(included: true) }
@@ -87,6 +88,10 @@ module Spree
87
88
  source_type == 'Spree::PromotionAction'
88
89
  end
89
90
 
91
+ def tax?
92
+ source_type == 'Spree::TaxRate'
93
+ end
94
+
90
95
  # Passing a target here would always be recommended as it would avoid
91
96
  # hitting the database again and would ensure you're compute values over
92
97
  # the specific object amount passed here.
@@ -8,5 +8,13 @@ module Spree
8
8
 
9
9
  belongs_to :viewable, polymorphic: true, touch: true
10
10
  acts_as_list scope: [:viewable_id, :viewable_type]
11
+
12
+ if Spree.public_storage_service_name
13
+ has_one_attached :attachment, service: Spree.public_storage_service_name
14
+ else
15
+ has_one_attached :attachment
16
+ end
17
+
18
+ default_scope { includes(attachment_attachment: :blob) }
11
19
  end
12
20
  end
@@ -4,12 +4,15 @@ module Spree
4
4
  class Calculator::FlatRate < Calculator
5
5
  preference :amount, :decimal, default: 0
6
6
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
7
+ preference :apply_only_on_full_priced_items, :boolean, default: false
7
8
 
8
9
  def self.description
9
10
  Spree.t(:flat_rate_per_order)
10
11
  end
11
12
 
12
13
  def compute(object = nil)
14
+ return 0 if preferred_apply_only_on_full_priced_items && object&.variant&.compare_at_amount_in(object.currency).present?
15
+
13
16
  if object && preferred_currency.casecmp(object.currency.upcase).zero?
14
17
  preferred_amount
15
18
  else
@@ -6,6 +6,7 @@ module Spree
6
6
  preference :additional_item, :decimal, default: 0.0
7
7
  preference :max_items, :integer, default: 0
8
8
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
9
+ preference :apply_only_on_full_priced_items, :boolean, default: false
9
10
 
10
11
  def self.description
11
12
  Spree.t(:flexible_rate)
@@ -16,6 +17,8 @@ module Spree
16
17
  end
17
18
 
18
19
  def compute(object)
20
+ return 0 if preferred_apply_only_on_full_priced_items && object.variant.compare_at_amount_in(object.currency).present?
21
+
19
22
  compute_from_quantity(object.quantity)
20
23
  end
21
24
 
@@ -2,12 +2,15 @@ module Spree
2
2
  class Calculator
3
3
  class PercentOnLineItem < Calculator
4
4
  preference :percent, :decimal, default: 0
5
+ preference :apply_only_on_full_priced_items, :boolean, default: false
5
6
 
6
7
  def self.description
7
8
  Spree.t(:percent_per_item)
8
9
  end
9
10
 
10
11
  def compute(object)
12
+ return 0 if preferred_apply_only_on_full_priced_items && object.variant.compare_at_amount_in(object.currency).present?
13
+
11
14
  computed_amount = (object.amount * preferred_percent / 100).round(2)
12
15
 
13
16
  # We don't want to cause the promotion adjustments to push the order into a negative total.
@@ -6,11 +6,23 @@ module Spree
6
6
  preference :amount, :decimal, default: 0
7
7
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
8
8
 
9
+ preference :minimum_item_total, :decimal, default: nil, nullable: true
10
+ preference :maximum_item_total, :decimal, default: nil, nullable: true
11
+
12
+ preference :minimum_weight, :decimal, default: nil, nullable: true
13
+ preference :maximum_weight, :decimal, default: nil, nullable: true
14
+
9
15
  def self.description
10
16
  Spree.t(:shipping_flat_rate_per_order)
11
17
  end
12
18
 
13
- def compute_package(_package)
19
+ def compute_package(package)
20
+ return nil if preferred_minimum_weight.present? && preferred_minimum_weight >= package.weight
21
+ return nil if preferred_maximum_weight.present? && preferred_maximum_weight < package.weight
22
+
23
+ return nil if preferred_minimum_item_total.present? && preferred_minimum_item_total >= package.item_total
24
+ return nil if preferred_maximum_item_total.present? && preferred_maximum_item_total < package.item_total
25
+
14
26
  preferred_amount
15
27
  end
16
28
  end
@@ -1,11 +1,5 @@
1
1
  module Spree
2
2
  class CmsSectionImage < Asset
3
- if Spree.public_storage_service_name
4
- has_one_attached :attachment, service: Spree.public_storage_service_name
5
- else
6
- has_one_attached :attachment
7
- end
8
-
9
3
  IMAGE_COUNT = ['one', 'two', 'three']
10
4
  IMAGE_TYPES = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'].freeze
11
5
  IMAGE_SIZE = ['sm', 'md', 'lg', 'xl']
@@ -45,6 +45,7 @@ module Spree
45
45
  # Temporarily tie a customer_return to one order
46
46
  def order
47
47
  return nil if return_items.blank?
48
+ return nil if return_items.first.inventory_unit.blank?
48
49
 
49
50
  return_items.first.inventory_unit.order
50
51
  end
@@ -1,11 +1,5 @@
1
1
  module Spree
2
2
  class Icon < Spree::Asset
3
- if Spree.public_storage_service_name
4
- has_one_attached :attachment, service: Spree.public_storage_service_name
5
- else
6
- has_one_attached :attachment
7
- end
8
-
9
3
  ICON_TYPES = %i[png jpg jpeg gif svg]
10
4
 
11
5
  validates :attachment, attached: true, content_type: ICON_TYPES
@@ -5,16 +5,8 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- if Spree.public_storage_service_name
9
- has_one_attached :attachment, service: Spree.public_storage_service_name
10
- else
11
- has_one_attached :attachment
12
- end
13
-
14
8
  validates :attachment, attached: true, content_type: /\Aimage\/.*\z/
15
9
 
16
- default_scope { includes(attachment_attachment: :blob) }
17
-
18
10
  def self.styles
19
11
  @styles ||= {
20
12
  mini: '48x48>',
@@ -4,6 +4,8 @@ module Spree
4
4
  include Rails.application.routes.url_helpers
5
5
  include Spree::ImageMethods
6
6
 
7
+ after_commit :touch_product_variants, if: :should_touch_product_variants?, on: :update
8
+
7
9
  # In Rails 5.x class constants are being undefined/redefined during the code reloading process
8
10
  # in a rails development environment, after which the actual ruby objects stored in those class constants
9
11
  # are no longer equal (subclass == self) what causes error ActiveRecord::SubclassNotFound
@@ -51,5 +53,20 @@ module Spree
51
53
  def plp_url
52
54
  generate_url(size: self.class.styles[:plp_and_carousel])
53
55
  end
56
+
57
+ private
58
+
59
+ def touch_product_variants
60
+ viewable.product.variants.touch_all
61
+ end
62
+
63
+ def should_touch_product_variants?
64
+ return false unless viewable.is_a?(Spree::Variant)
65
+ return false unless viewable.is_master?
66
+ return false unless viewable.product.has_variants?
67
+ return false unless saved_change_to_position?
68
+
69
+ true
70
+ end
54
71
  end
55
72
  end
@@ -13,7 +13,7 @@ module Spree
13
13
  end
14
14
  belongs_to :tax_category, -> { with_deleted }, class_name: 'Spree::TaxCategory'
15
15
 
16
- has_one :product, through: :variant
16
+ has_one :product, -> { with_deleted }, class_name: 'Spree::Product', through: :variant
17
17
 
18
18
  has_many :adjustments, as: :adjustable, dependent: :destroy
19
19
  has_many :inventory_units, inverse_of: :line_item
@@ -70,11 +70,7 @@ module Spree
70
70
  def update_price
71
71
  currency_price = variant.price_in(order.currency)
72
72
 
73
- self.price = if currency_price.amount.present?
74
- currency_price.price_including_vat_for(tax_zone: tax_zone)
75
- else
76
- 0
77
- end
73
+ self.price = currency_price.price_including_vat_for(tax_zone: tax_zone) if currency_price.present?
78
74
  end
79
75
 
80
76
  def copy_tax_category
@@ -99,6 +95,13 @@ module Spree
99
95
  amount + taxable_adjustment_total
100
96
  end
101
97
 
98
+ # returns the total tax amount
99
+ #
100
+ # @return [BigDecimal]
101
+ def tax_total
102
+ included_tax_total + additional_tax_total
103
+ end
104
+
102
105
  alias discounted_money display_discounted_amount
103
106
  alias discounted_amount taxable_amount
104
107
 
@@ -117,6 +120,20 @@ module Spree
117
120
  !sufficient_stock?
118
121
  end
119
122
 
123
+ # returns true if any of the inventory units are shipped
124
+ #
125
+ # @return [Boolean]
126
+ def any_shipped?
127
+ inventory_units.any?(&:shipped?)
128
+ end
129
+
130
+ # returns true if all of the inventory units are shipped
131
+ #
132
+ # @return [Boolean]
133
+ def fully_shipped?
134
+ inventory_units.all?(&:shipped?)
135
+ end
136
+
120
137
  def options=(options = {})
121
138
  return unless options.present?
122
139
 
@@ -5,5 +5,9 @@ module Spree
5
5
 
6
6
  validates :option_value, :variant, presence: true
7
7
  validates :option_value_id, uniqueness: { scope: :variant_id }
8
+
9
+ scope :for_option_types, lambda { |option_types|
10
+ joins(:option_value).merge(Spree::OptionValue.where(option_types: option_types))
11
+ }
8
12
  end
9
13
  end
@@ -26,10 +26,10 @@ module Spree
26
26
  def update_line_item_price!(line_item)
27
27
  price = price_from_line_item(line_item)
28
28
 
29
- if price
29
+ if price&.currency && price.amount
30
30
  line_item.update!(currency: price.currency, price: price.amount)
31
31
  else
32
- raise "no #{currency} price found for #{line_item.product.name} (#{line_item.variant.sku})"
32
+ line_item.destroy
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ class Order < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+
6
+ def send_order_canceled_webhook
7
+ # Implement your logic of sending cancale webhooks
8
+ end
9
+
10
+ def send_order_placed_webhook
11
+ # Implement your logic of sending order placed webhooks
12
+ end
13
+
14
+ def send_order_resumed_webhook
15
+ # Implement your logic of sending after resume webhooks
16
+ end
17
+ end
18
+ end
19
+ end
@@ -17,6 +17,7 @@ module Spree
17
17
  include Spree::Order::StoreCredit
18
18
  include Spree::Order::AddressBook
19
19
  include Spree::Order::Emails
20
+ include Spree::Order::Webhooks
20
21
  include Spree::Core::NumberGenerator.new(prefix: 'R')
21
22
  include Spree::Core::TokenGenerator
22
23
 
@@ -25,9 +26,6 @@ module Spree
25
26
  include Spree::SingleStoreResource
26
27
  include Spree::MemoizedData
27
28
  include Spree::Metadata
28
- if defined?(Spree::Webhooks::HasWebhooks)
29
- include Spree::Webhooks::HasWebhooks
30
- end
31
29
  if defined?(Spree::Security::Orders)
32
30
  include Spree::Security::Orders
33
31
  end
@@ -91,9 +89,9 @@ module Spree
91
89
  belongs_to :user, optional: true
92
90
  end
93
91
  if Spree.admin_user_class
94
- belongs_to :created_by, class_name: Spree.admin_user_class.to_s, optional: true
95
- belongs_to :approver, class_name: Spree.admin_user_class.to_s, optional: true
96
- belongs_to :canceler, class_name: Spree.admin_user_class.to_s, optional: true
92
+ belongs_to :created_by, class_name: "::#{Spree.admin_user_class}", optional: true
93
+ belongs_to :approver, class_name: "::#{Spree.admin_user_class}", optional: true
94
+ belongs_to :canceler, class_name: "::#{Spree.admin_user_class}", optional: true
97
95
  else
98
96
  belongs_to :created_by, optional: true
99
97
  belongs_to :approver, optional: true
@@ -401,6 +399,8 @@ module Spree
401
399
  deliver_order_confirmation_email unless confirmation_delivered?
402
400
  deliver_store_owner_order_notification_email if deliver_store_owner_order_notification_email?
403
401
 
402
+ send_order_placed_webhook
403
+
404
404
  consider_risk
405
405
  end
406
406
 
@@ -522,6 +522,14 @@ module Spree
522
522
  self.shipments = Spree::Stock::Coordinator.new(self).shipments
523
523
  end
524
524
 
525
+ # Returns the total weight of the inventory units in the order
526
+ # This is used to calculate the shipping rates for the order
527
+ #
528
+ # @return [BigDecimal] the total weight of the inventory units in the order
529
+ def total_weight
530
+ @total_weight ||= line_items.joins(:variant).includes(:variant).map { |li| li.variant.weight * li.quantity }.sum
531
+ end
532
+
525
533
  def apply_free_shipping_promotions
526
534
  Spree::PromotionHandler::FreeShipping.new(self).activate
527
535
  shipments.each { |shipment| Spree::Adjustable::AdjustmentsUpdater.update(shipment) }
@@ -566,6 +574,7 @@ module Spree
566
574
  def set_shipments_cost
567
575
  shipments.each(&:update_amounts)
568
576
  updater.update_shipment_total
577
+ updater.update_adjustment_total
569
578
  persist_totals
570
579
  end
571
580
 
@@ -726,11 +735,13 @@ module Spree
726
735
 
727
736
  send_cancel_email
728
737
  update_with_updater!
738
+ send_order_canceled_webhook
729
739
  end
730
740
 
731
741
  def after_resume
732
742
  shipments.each(&:resume!)
733
743
  consider_risk
744
+ send_order_resumed_webhook
734
745
  end
735
746
 
736
747
  def use_billing?
@@ -16,6 +16,7 @@ module Spree
16
16
  end
17
17
 
18
18
  set_user(user)
19
+ clear_addresses(other_order)
19
20
  persist_merge
20
21
 
21
22
  # So that the destroy doesn't take out line items which may have been re-assigned
@@ -39,6 +40,11 @@ module Spree
39
40
  order.associate_user!(user) if !order.user && !user.blank?
40
41
  end
41
42
 
43
+ def clear_addresses(other_order)
44
+ other_order.ship_address = nil
45
+ other_order.bill_address = nil
46
+ end
47
+
42
48
  # The idea is the end developer can choose to override the merge
43
49
  # to their own choosing. Default is merge with errors.
44
50
  def handle_merge(current_line_item, other_order_line_item)
@@ -5,6 +5,10 @@ module Spree
5
5
  @payment = payment
6
6
  end
7
7
 
8
+ def statement_descriptor_suffix
9
+ order.number
10
+ end
11
+
8
12
  def email
9
13
  order.email
10
14
  end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ class Payment < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+
6
+ def send_payment_voided_webhook
7
+ # Implement your logic here
8
+ end
9
+
10
+ def send_payment_completed_webhook
11
+ # Implement your logic here
12
+ end
13
+ end
14
+ end
15
+ end
@@ -6,14 +6,12 @@ module Spree
6
6
  include Spree::NumberIdentifier
7
7
  include Spree::NumberAsParam
8
8
  include Spree::Metadata
9
- if defined?(Spree::Webhooks::HasWebhooks)
10
- include Spree::Webhooks::HasWebhooks
11
- end
12
9
  if defined?(Spree::Security::Payments)
13
10
  include Spree::Security::Payments
14
11
  end
15
12
 
16
13
  include Spree::Payment::Processing
14
+ include Spree::Payment::Webhooks
17
15
 
18
16
  NON_RISKY_AVS_CODES = ['B', 'D', 'H', 'J', 'M', 'Q', 'T', 'V', 'X', 'Y'].freeze
19
17
  RISKY_AVS_CODES = ['A', 'C', 'E', 'F', 'G', 'I', 'K', 'L', 'N', 'O', 'P', 'R', 'S', 'U', 'W', 'Z'].freeze
@@ -67,6 +65,8 @@ module Spree
67
65
  scope :processing, -> { with_state('processing') }
68
66
  scope :failed, -> { with_state('failed') }
69
67
 
68
+ scope :incomplete, -> { where.not(state: 'completed') }
69
+
70
70
  scope :risky, -> { where("avs_response IN (?) OR (cvv_response_code IS NOT NULL and cvv_response_code != 'M') OR state = 'failed'", RISKY_AVS_CODES) }
71
71
  scope :valid, -> { where.not(state: INVALID_STATES) }
72
72
 
@@ -105,11 +105,11 @@ module Spree
105
105
  event :complete do
106
106
  transition from: [:processing, :pending, :checkout], to: :completed
107
107
  end
108
- after_transition to: :completed, do: :after_completed
108
+ after_transition to: :completed, do: [:after_completed, :send_payment_completed_webhook]
109
109
  event :void do
110
110
  transition from: [:pending, :processing, :completed, :checkout], to: :void
111
111
  end
112
- after_transition to: :void, do: :after_void
112
+ after_transition to: :void, do: [:after_void, :send_payment_voided_webhook]
113
113
  # when the card brand isn't supported
114
114
  event :invalidate do
115
115
  transition from: [:checkout], to: :invalid
@@ -209,11 +209,11 @@ module Spree
209
209
  private
210
210
 
211
211
  def after_void
212
- # this method is prepended in api/ to queue Webhooks requests
212
+ # Implement your logic here
213
213
  end
214
214
 
215
215
  def after_completed
216
- # this method is prepended in api/ to queue Webhooks requests
216
+ # Implement your logic here
217
217
  end
218
218
 
219
219
  def has_invalid_state?
@@ -12,6 +12,7 @@ module Spree
12
12
  belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant', inverse_of: :prices, touch: true
13
13
 
14
14
  before_validation :ensure_currency
15
+ before_save :remove_compare_at_amount_if_equals_amount
15
16
 
16
17
  validates :amount, allow_nil: true, numericality: {
17
18
  greater_than_or_equal_to: 0,
@@ -23,6 +24,10 @@ module Spree
23
24
  less_than_or_equal_to: MAXIMUM_AMOUNT
24
25
  }
25
26
 
27
+ scope :with_currency, ->(currency) { where(currency: currency) }
28
+ scope :non_zero, -> { where.not(amount: [nil, 0]) }
29
+ scope :discounted, -> { where('compare_at_amount > amount') }
30
+
26
31
  extend DisplayMoney
27
32
  money_methods :amount, :price, :compare_at_amount
28
33
  alias display_compare_at_price display_compare_at_amount
@@ -68,10 +73,50 @@ module Spree
68
73
  Spree::Money.new(compare_at_price_including_vat_for(price_options), currency: currency)
69
74
  end
70
75
 
76
+ # returns the name of the price in a format of variant name and currency
77
+ #
78
+ # @return [String]
79
+ def name
80
+ "#{variant.name} - #{currency.upcase}"
81
+ end
82
+
83
+ # returns true if the price is discounted
84
+ #
85
+ # @return [Boolean]
86
+ def discounted?
87
+ compare_at_amount.to_i.positive? && compare_at_amount > amount
88
+ end
89
+
90
+ # returns true if the price was discounted
91
+ #
92
+ # @return [Boolean]
93
+ def was_discounted?
94
+ compare_at_amount_was.to_i.positive? && compare_at_amount_was > amount_was
95
+ end
96
+
97
+ # returns true if the price is zero
98
+ #
99
+ # @return [Boolean]
100
+ def zero?
101
+ amount.nil? || amount.zero?
102
+ end
103
+
104
+ # returns true if the price is not zero
105
+ #
106
+ # @return [Boolean]
107
+ def non_zero?
108
+ !zero?
109
+ end
110
+
71
111
  private
72
112
 
73
113
  def ensure_currency
74
114
  self.currency ||= Spree::Store.default.default_currency
75
115
  end
116
+
117
+ # removes the compare at amount if it is the same as the amount
118
+ def remove_compare_at_amount_if_equals_amount
119
+ self.compare_at_amount = nil if compare_at_amount == amount
120
+ end
76
121
  end
77
122
  end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ class Product < Spree::Base
3
+ module Webhooks
4
+ def send_product_activated_webhook
5
+ # Implement your logic here
6
+ end
7
+
8
+ def send_product_archived_webhook
9
+ # Implement your logic here
10
+ end
11
+
12
+ def send_product_drafted_webhook
13
+ # Implement your logic here
14
+ end
15
+ end
16
+ end
17
+ end