spree_core 4.8.2 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) 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/locale.rb +1 -0
  72. data/lib/spree/core/controller_helpers/order.rb +5 -5
  73. data/lib/spree/core/dependencies.rb +2 -1
  74. data/lib/spree/core/preferences/preferable.rb +4 -2
  75. data/lib/spree/core/preferences/preferable_class_methods.rb +3 -2
  76. data/lib/spree/core/version.rb +1 -1
  77. data/lib/spree/core/webhooks.rb +15 -7
  78. data/lib/spree/core.rb +1 -0
  79. data/lib/spree/money.rb +1 -1
  80. data/lib/spree/testing_support/authorization_helpers.rb +2 -2
  81. data/lib/spree/testing_support/common_rake.rb +1 -1
  82. data/spree_core.gemspec +1 -0
  83. metadata +31 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 347d9720ab5b7339d59ea9a0bf03052f1574b3b4acaceb034063afa0c22515a8
4
- data.tar.gz: 9de3e8196d36b1131f130360c5fab3f202d9f60af7a31470d94ebbd96b50903b
3
+ metadata.gz: 3966d98b01b3e3ac7e23384c37933bfd0cd961d05a112b6b538e3973bf62e0df
4
+ data.tar.gz: d1305f1376e401557ae786799c862ab927c4aa26986cb444bdd8db4230d92cd8
5
5
  SHA512:
6
- metadata.gz: 075bf6bcf2f617036128adf641e401d22a97942117fc2b1fc553a33456697618a43207454c592ff83bc51ef8eaf585fc7fc55fb62c7092f062366f89b6c8a859
7
- data.tar.gz: 24577e9f4781111d8fddb27b9bf7735d5ad67f29d8aea28b1b16e7b6bd17c96490a7a867ffe0be1760ef4932e6b423246362fe884f6b6da33be296d3b1886bc7
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