spree_api 4.8.3 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +57 -0
  3. data/app/controllers/concerns/spree/api/v2/caching.rb +12 -9
  4. data/app/controllers/concerns/spree/api/v2/storefront/order_concern.rb +2 -2
  5. data/app/controllers/spree/api/v2/base_controller.rb +7 -1
  6. data/app/controllers/spree/api/v2/platform/resource_controller.rb +1 -1
  7. data/app/controllers/spree/api/v2/storefront/checkout_controller.rb +1 -1
  8. data/app/controllers/spree/api/v2/storefront/products_controller.rb +8 -1
  9. data/app/controllers/spree/api/v2/storefront/variants_controller.rb +41 -0
  10. data/app/controllers/spree/api/v2/storefront/wishlists_controller.rb +43 -0
  11. data/app/models/concerns/spree/webhooks/has_webhooks.rb +13 -5
  12. data/app/models/spree/oauth_application.rb +7 -0
  13. data/app/models/spree/order/webhooks.rb +39 -0
  14. data/app/models/spree/payment/webhooks.rb +23 -0
  15. data/app/models/spree/product/webhooks.rb +42 -0
  16. data/app/models/spree/shipment/webhooks.rb +19 -0
  17. data/app/models/spree/stock_item/webhooks.rb +40 -0
  18. data/app/models/spree/stock_movement/webhooks.rb +49 -0
  19. data/app/models/spree/variant/webhooks.rb +25 -0
  20. data/app/models/spree/webhooks/subscriber.rb +4 -0
  21. data/app/serializers/spree/api/v2/platform/admin_user_serializer.rb +11 -0
  22. data/app/serializers/spree/api/v2/platform/refund_serializer.rb +1 -0
  23. data/app/serializers/spree/api/v2/platform/store_credit_serializer.rb +1 -1
  24. data/app/serializers/spree/v2/storefront/credit_card_serializer.rb +1 -1
  25. data/app/serializers/spree/v2/storefront/shipping_category_serializer.rb +10 -0
  26. data/app/serializers/spree/v2/storefront/shipping_method_serializer.rb +10 -0
  27. data/app/services/spree/api/error_handler.rb +1 -0
  28. data/app/services/spree/webhooks/subscribers/handle_request.rb +4 -4
  29. data/app/services/spree/webhooks/subscribers/make_request.rb +2 -2
  30. data/app/services/spree/webhooks/subscribers/queue_requests.rb +3 -3
  31. data/config/initializers/doorkeeper.rb +2 -0
  32. data/config/routes.rb +4 -1
  33. data/lib/spree/api/configuration.rb +3 -1
  34. data/lib/spree/api/dependencies.rb +5 -1
  35. data/lib/spree/api/engine.rb +0 -8
  36. data/lib/spree/api/testing_support/matchers/webhooks.rb +4 -4
  37. data/lib/spree/api/testing_support/spree_webhooks.rb +2 -2
  38. data/spree_api.gemspec +3 -3
  39. metadata +22 -15
  40. data/LICENSE +0 -26
  41. data/app/models/spree/api/webhooks/order_decorator.rb +0 -43
  42. data/app/models/spree/api/webhooks/payment_decorator.rb +0 -26
  43. data/app/models/spree/api/webhooks/product_decorator.rb +0 -46
  44. data/app/models/spree/api/webhooks/shipment_decorator.rb +0 -21
  45. data/app/models/spree/api/webhooks/stock_item_decorator.rb +0 -43
  46. data/app/models/spree/api/webhooks/stock_movement_decorator.rb +0 -52
  47. data/app/models/spree/api/webhooks/variant_decorator.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 870a75c20df08101daae7df947e3dfd285a831eefe5e30105389771bcf5e578e
4
- data.tar.gz: edc2ee5df0c726087fa837456d38d271c27a35fa95762b1c2c422763fb132fd3
3
+ metadata.gz: 4c9439192de12b6b1178bee80776eb695521403795e642f0a2705371046127cb
4
+ data.tar.gz: 22d7e3c800a68a29e4c8a6c5d9a5ec56050b32aa96c64d0a988259293b5e3c59
5
5
  SHA512:
6
- metadata.gz: 414c7d4eeb5b12c336b63f8ce4f8a237a7bb38f372bef045926d201df72078bb0e17613496a6ef5e47c89554e95283f4d4f510f85e020f555a9b05763b3826e0
7
- data.tar.gz: 241adceec83317dda28aca5a2183c44d3906af1b197a3b0d41b0fd86fdd6ef88f7780135633b1e8da49824374752c28dcc9f354f5f05433c1ac7c2d01a3d3a67
6
+ metadata.gz: f263e4c2eceb996112faef3810743bbde167c048a21899ae32d4fd965f03c6f6666bf6587b12ad4f6fdcb2c45d0b97261dd76102f259d2a792268c12ac919fb3
7
+ data.tar.gz: 4b77cbc8ad82234718f992ea8482fb8c10931efb2b74e3a3aae1fe1662df862ec56f114f23a3426dda5993c102e5a0843abddb5c6fa294c5724fcac8b9e6c699
data/LICENSE.md ADDED
@@ -0,0 +1,57 @@
1
+ # License
2
+
3
+ Copyright © 2024-present, Vendo Connect Inc.
4
+
5
+ Copyright © 2015-2024, Spark Solutions Sp. z o.o.
6
+
7
+ Copyright © 2007-2015, Spree Commerce Inc.
8
+
9
+ Spree Commerce is a free, open-source eCommerce framework giving you full control and customizability.
10
+
11
+ For **Spree Commerce versions 4.10** and later in the [spree/spree](https://github.com/spree/spree) repository two licenses apply simultaneously and users are required to comply with the terms of these two licenses at the same time:
12
+
13
+ * [AGPL-3.0](https://opensource.org/license/agpl-v3) - for all contributions from version 4.10 onwards
14
+
15
+ * [BSD-3-Clause](https://opensource.org/license/bsd-3-clause) - for all other contributions predating version 4.10
16
+ Effectively, for versions 4.10 and upwards **AGPL-3.0** license applies.
17
+
18
+ **Spree Commerce versions 4.9** and earlier in the [spree/spree](https://github.com/spree/spree) repository are available under the **BSD-3-Clause** license and users are required to comply with its terms.
19
+
20
+ If you’d like to use Spree Commerce without the AGPL-3.0 restrictions e.g. for a **SaaS** business, please talk to us about obtaining a [Commercial License](#commercial-license).
21
+
22
+ All third party components incorporated into this software are licensed under the original license provided by the owner of the applicable component.
23
+
24
+ Please refer to our [Licensing FAQ](https://spreecommerce.org/why-spree-is-changing-its-open-source-license-to-agpl-3-0-and-introducing-a-commercial-license/) in case of questions.
25
+
26
+ ## AGPL-3.0 License - Spree 4.10 upwards
27
+
28
+ If you decide to accept the AGPL-3.0 license by using Spree Commerce version 4.10 or later, you must comply with the following terms:
29
+
30
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
31
+
32
+ This program is distributed in the hope that it will be useful,
33
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
34
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
+ GNU Affero General Public License for more details.
36
+
37
+ You should have received a copy of the GNU Affero General Public License
38
+ along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
39
+
40
+ ## BSD 3-Clause License
41
+
42
+ If you decide to accept the BSD-3-Clause license by using Spree Commerce, you must comply with the following terms:
43
+
44
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
45
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
46
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
47
+ Neither the name of Spree Commerce Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
48
+
49
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50
+
51
+ ## Commercial License
52
+
53
+ Should you decide to use Spree Commerce version 4.10 or later for commercial redistribution (eg. SaaS) - also known as Commercial Distribution - you must do so in accordance with the terms and conditions contained in a separate written agreement between you and [Vendo Connect Inc.](https://www.getvendo.com/).
54
+
55
+ For more information about the Commercial License (CL) please contact [sales@getvendo.com](mailto:sales@getvendo.com).
56
+
57
+ Please refer to our [Licensing FAQ](https://spreecommerce.org/why-spree-is-changing-its-open-source-license-to-agpl-3-0-and-introducing-a-commercial-license/) in case of questions.
@@ -5,22 +5,19 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  def collection_cache_key(collection)
8
- ids_and_timestamps = collection.unscope(:includes).unscope(:order).pluck(:id, :updated_at)
9
-
10
- ids = ids_and_timestamps.map(&:first)
11
- max_updated_at = ids_and_timestamps.map(&:last).max
8
+ params.delete(:page) if params[:page] == 1
12
9
 
13
10
  cache_key_parts = [
14
- self.class.to_s,
15
- max_updated_at,
16
- ids,
11
+ collection.cache_key_with_version,
17
12
  resource_includes,
18
13
  sparse_fields,
19
- serializer_params,
14
+ serializer_params_cache_key,
20
15
  params[:sort]&.strip,
21
16
  params[:page]&.to_s&.strip,
22
17
  params[:per_page]&.to_s&.strip,
23
- ].flatten.join('-')
18
+ ]
19
+ cache_key_parts += additional_cache_key_parts if defined?(additional_cache_key_parts)
20
+ cache_key_parts = cache_key_parts.flatten.join('-')
24
21
 
25
22
  Digest::MD5.hexdigest(cache_key_parts)
26
23
  end
@@ -31,6 +28,12 @@ module Spree
31
28
  expires_in: Spree::Api::Config[:api_v2_collection_cache_ttl],
32
29
  }
33
30
  end
31
+
32
+ def serializer_params_cache_key
33
+ serializer_params.values.map do |param|
34
+ param.try(:cache_key) || param.try(:flatten).try(:join, '-') || param.try(:to_s)
35
+ end.compact.join('-')
36
+ end
34
37
  end
35
38
  end
36
39
  end
@@ -9,7 +9,7 @@ module Spree
9
9
  if result.success?
10
10
  render_serialized_payload { serialized_current_order }
11
11
  else
12
- render_error_payload(result.error)
12
+ render_error_payload(result.error&.value || result.value)
13
13
  end
14
14
  end
15
15
 
@@ -18,7 +18,7 @@ module Spree
18
18
  end
19
19
 
20
20
  def order_token
21
- request.headers['X-Spree-Order-Token'] || params[:order_token]
21
+ request.headers['X-Vendo-Order-Token'] || request.headers['X-Spree-Order-Token'] || params[:order_token]
22
22
  end
23
23
 
24
24
  def spree_current_order
@@ -142,11 +142,15 @@ module Spree
142
142
  }
143
143
  end
144
144
 
145
- def record_not_found
145
+ def record_not_found(exception)
146
+ result = error_handler.call(exception: exception, opts: { user: spree_current_user })
147
+
146
148
  render_error_payload(I18n.t(:resource_not_found, scope: 'spree.api'), 404)
147
149
  end
148
150
 
149
151
  def access_denied(exception)
152
+ result = error_handler.call(exception: exception, opts: { user: spree_current_user })
153
+
150
154
  render_error_payload(exception.message, 403)
151
155
  end
152
156
 
@@ -155,6 +159,8 @@ module Spree
155
159
  end
156
160
 
157
161
  def gateway_error(exception)
162
+ result = error_handler.call(exception: exception, opts: { user: spree_current_user })
163
+
158
164
  render_error_payload(exception.message)
159
165
  end
160
166
 
@@ -88,7 +88,7 @@ module Spree
88
88
  end
89
89
 
90
90
  # if using a user oAuth token we need to check CanCanCan abilities
91
- # defined in https://github.com/spree/spree/blob/master/core/app/models/spree/ability.rb
91
+ # defined in https://github.com/spree/spree/blob/main/core/app/models/spree/ability.rb
92
92
  def authorize_spree_user
93
93
  return if spree_current_user.nil?
94
94
 
@@ -36,7 +36,7 @@ module Spree
36
36
  result = update_service.call(
37
37
  order: spree_current_order,
38
38
  params: params,
39
- # defined in https://github.com/spree/spree/blob/master/core/lib/spree/core/controller_helpers/strong_parameters.rb#L19
39
+ # defined in https://github.com/spree/spree/blob/main/core/lib/spree/core/controller_helpers/strong_parameters.rb#L19
40
40
  permitted_attributes: permitted_checkout_attributes,
41
41
  request_env: request.headers.env
42
42
  )
@@ -16,7 +16,14 @@ module Spree
16
16
  end
17
17
 
18
18
  def resource
19
- @resource ||= find_with_fallback_default_locale { scope.find_by(slug: params[:id]) } || scope.find(params[:id])
19
+ # fixed N+1 queries issue
20
+ variant_includes = [
21
+ :prices,
22
+ { option_values: [:option_type] }
23
+ ]
24
+
25
+ # using FriendlyId so old slugs still work
26
+ @resource ||= find_with_fallback_default_locale { scope.includes(variants: variant_includes, master: variant_includes).friendly.find(params[:id]) } || scope.includes(variants: variant_includes, master: variant_includes).friendly.find(params[:id])
20
27
  end
21
28
 
22
29
  def collection_sorter
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class VariantsController < ::Spree::Api::V2::ResourceController
6
+ private
7
+
8
+ def model_class
9
+ Spree::Variant
10
+ end
11
+
12
+ def scope
13
+ product_scope = Spree::Product.for_store(current_store)
14
+ product_scope = product_scope.accessible_by(current_ability, :show)
15
+ product_scope = product_scope.i18n if model_class.include?(TranslatableResource)
16
+
17
+ product = product_scope.friendly.find(params[:id])
18
+ product.variants_including_master
19
+ end
20
+
21
+ def collection_finder
22
+ Spree::Api::Dependencies.storefront_variant_finder.constantize
23
+ end
24
+
25
+ def collection_serializer
26
+ Spree::Api::Dependencies.storefront_variant_serializer.constantize
27
+ end
28
+
29
+ def scope_includes
30
+ variant_includes = {
31
+ prices: [],
32
+ option_values: :option_type,
33
+ }
34
+ variant_includes[:images] = [] if params[:include]&.match(/images/)
35
+ variant_includes
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -100,6 +100,41 @@ module Spree
100
100
  end
101
101
  end
102
102
 
103
+ def add_items
104
+ spree_authorize! :create, Spree::WishedItem
105
+
106
+ @wished_items = resource.wished_items
107
+ ApplicationRecord.transaction do
108
+ wished_items_attributes.each do |wished_item_attributes|
109
+ wished_item = @wished_items.find_by(variant_id: wished_item_attributes[:variant_id])
110
+
111
+ if wished_item
112
+ wished_item.update!(quantity: wished_item_attributes[:quantity] || 1)
113
+ else
114
+ Spree::WishedItem.create!(wished_item_attributes.merge(wishlist: resource))
115
+ end
116
+ end
117
+ end
118
+
119
+ resource.reload
120
+ render_serialized_payload { serialize_resource(resource) }
121
+ rescue ActiveRecord::RecordInvalid => e
122
+ render json: { error: e.record.errors.full_messages.to_sentence }, status: 422
123
+ end
124
+
125
+ def remove_items
126
+ spree_authorize! :destroy, Spree::WishedItem
127
+
128
+ ApplicationRecord.transaction do
129
+ resource.wished_items.where(id: wished_items_ids).each(&:destroy!)
130
+ end
131
+
132
+ resource.reload
133
+ render_serialized_payload { serialize_resource(resource) }
134
+ rescue ActiveRecord::RecordNotDestroyed => e
135
+ render json: { error: e.record.errors.full_messages.to_sentence }, status: 422
136
+ end
137
+
103
138
  private
104
139
 
105
140
  def scope(skip_cancancan: true)
@@ -164,6 +199,14 @@ module Spree
164
199
  1
165
200
  end
166
201
  end
202
+
203
+ def wished_items_attributes
204
+ params.permit(wished_items: permitted_wished_item_attributes).require(:wished_items)
205
+ end
206
+
207
+ def wished_items_ids
208
+ params.require(:wished_items_ids)
209
+ end
167
210
  end
168
211
  end
169
212
  end
@@ -10,13 +10,15 @@ module Spree
10
10
 
11
11
  def queue_webhooks_requests!(event_name)
12
12
  return if disable_spree_webhooks?
13
- return if Spree::Webhooks::Subscriber.none?
14
- return if Spree::Webhooks::Subscriber.active.with_urls_for(event_name).none?
13
+ return if no_webhooks_subscribers?(event_name)
15
14
  return if update_event?(event_name) && updating_only_ignored_attributes?
16
15
  return if webhook_payload_body.blank?
17
16
 
18
17
  Spree::Webhooks::Subscribers::QueueRequests.call(
19
- event_name: event_name, webhook_payload_body: webhook_payload_body, **webhooks_request_options
18
+ record: self,
19
+ event_name: event_name,
20
+ webhook_payload_body: webhook_payload_body,
21
+ **webhooks_request_options
20
22
  )
21
23
  end
22
24
 
@@ -43,7 +45,9 @@ module Spree
43
45
  end
44
46
 
45
47
  def included_relationships
46
- if resource_serializer.relationships_to_serialize
48
+ if self.class.respond_to?(:webhook_included_relationships)
49
+ self.class.webhook_included_relationships
50
+ elsif resource_serializer.relationships_to_serialize
47
51
  resource_serializer.relationships_to_serialize.keys
48
52
  else
49
53
  []
@@ -74,7 +78,11 @@ module Spree
74
78
  end
75
79
 
76
80
  def disable_spree_webhooks?
77
- ENV['DISABLE_SPREE_WEBHOOKS'] == 'true'
81
+ Spree::Webhooks.disabled?
82
+ end
83
+
84
+ def no_webhooks_subscribers?(event_name)
85
+ !Spree::Webhooks::Subscriber.active.with_urls_for(event_name).exists?
78
86
  end
79
87
 
80
88
  def webhooks_request_options
@@ -6,6 +6,13 @@ module Spree
6
6
 
7
7
  before_validation :set_blank_for_redirect_uri
8
8
 
9
+ # returns the last someone used this application
10
+ #
11
+ # @return [DateTime]
12
+ def last_used_at
13
+ access_tokens.order(:created_at).last&.created_at
14
+ end
15
+
9
16
  private
10
17
 
11
18
  def set_blank_for_redirect_uri
@@ -0,0 +1,39 @@
1
+ module Spree
2
+ class Order < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ after_update_commit :queue_webhooks_requests_for_order_resumed!
9
+ end
10
+
11
+ class_methods do
12
+ def custom_webhook_events
13
+ %w[order.canceled order.placed order.resumed order.shipped]
14
+ end
15
+ end
16
+
17
+ def send_order_canceled_webhook
18
+ queue_webhooks_requests!('order.canceled')
19
+ end
20
+
21
+ def send_order_placed_webhook
22
+ queue_webhooks_requests!('order.placed')
23
+ end
24
+
25
+ def send_order_resumed_webhook
26
+ queue_webhooks_requests!('order.resumed')
27
+ self.state_machine_resumed = false # to not fire the same webhook twice
28
+ end
29
+
30
+ def queue_webhooks_requests_for_order_resumed!
31
+ return if state_machine_resumed?
32
+ return unless state_previously_changed?
33
+ return unless state_previous_change&.last == 'resumed'
34
+
35
+ send_order_resumed_webhook
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ module Spree
2
+ class Payment < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ class_methods do
8
+ def custom_webhook_events
9
+ %w[payment.paid payment.voided]
10
+ end
11
+ end
12
+
13
+ def send_payment_voided_webhook
14
+ queue_webhooks_requests!('payment.voided')
15
+ end
16
+
17
+ def send_payment_completed_webhook
18
+ queue_webhooks_requests!('payment.paid')
19
+ order.queue_webhooks_requests!('order.paid') if order.paid?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ class Product < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ after_update_commit :queue_webhooks_requests_for_product_discontinued!
9
+ end
10
+
11
+ class_methods do
12
+ def custom_webhook_events
13
+ %w[product.back_in_stock product.backorderable product.discontinued
14
+ product.out_of_stock product.activated product.archived product.drafted]
15
+ end
16
+
17
+ def ignored_attributes_for_update_webhook_event
18
+ %w[status]
19
+ end
20
+ end
21
+
22
+ def send_product_activated_webhook
23
+ queue_webhooks_requests!('product.activated')
24
+ end
25
+
26
+ def send_product_archived_webhook
27
+ queue_webhooks_requests!('product.archived')
28
+ end
29
+
30
+ def send_product_drafted_webhook
31
+ queue_webhooks_requests!('product.drafted')
32
+ end
33
+
34
+ def queue_webhooks_requests_for_product_discontinued!
35
+ return unless discontinue_on_previously_changed?
36
+ return if (change = discontinue_on_previous_change).blank? || change.last.blank?
37
+
38
+ queue_webhooks_requests!('product.discontinued')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ class Shipment < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ class_methods do
8
+ def custom_webhook_events
9
+ %w[shipment.shipped]
10
+ end
11
+ end
12
+
13
+ def send_shipment_shipped_webhook
14
+ queue_webhooks_requests!('shipment.shipped')
15
+ order.queue_webhooks_requests!('order.shipped') if order.fully_shipped?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ class StockItem < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ around_save :queue_webhooks_requests_for_variant_backorderable!
9
+ around_save :queue_webhooks_requests_for_product_backorderable!
10
+ end
11
+
12
+ def queue_webhooks_requests_for_variant_backorderable!
13
+ was_out_of_stock = !variant.in_stock_or_backorderable?
14
+ was_not_backorderable = !variant_backorderable?
15
+ yield
16
+ if was_out_of_stock && was_not_backorderable && variant_backorderable?
17
+ reload
18
+ variant.queue_webhooks_requests!('variant.backorderable')
19
+ end
20
+ end
21
+
22
+ def queue_webhooks_requests_for_product_backorderable!
23
+ product_was_out_of_stock = !product.any_variant_in_stock_or_backorderable?
24
+ product_was_not_backorderable = !product_backorderable?
25
+ yield
26
+ if product_was_out_of_stock && product_was_not_backorderable && product_backorderable?
27
+ variant.product.queue_webhooks_requests!('product.backorderable')
28
+ end
29
+ end
30
+
31
+ def product_backorderable?
32
+ Spree::StockItem.exists?(backorderable: true, variant_id: variant.product.variants.ids)
33
+ end
34
+
35
+ def variant_backorderable?
36
+ variant.stock_items.exists?(backorderable: true)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ module Spree
2
+ class StockMovement < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ around_save :queue_webhooks_requests_for_variant_out_of_stock!
9
+ around_save :queue_webhooks_requests_for_variant_back_in_stock!
10
+ around_save :queue_webhooks_requests_for_product_out_of_stock!
11
+ around_save :queue_webhooks_requests_for_product_back_in_stock!
12
+ end
13
+
14
+ def queue_webhooks_requests_for_variant_out_of_stock!
15
+ variant_in_stock_before_update = variant.in_stock_or_backorderable?
16
+ yield
17
+ if variant_in_stock_before_update && !variant.in_stock_or_backorderable?
18
+ reload
19
+ stock_item.variant.queue_webhooks_requests!('variant.out_of_stock')
20
+ end
21
+ end
22
+
23
+ def queue_webhooks_requests_for_variant_back_in_stock!
24
+ variant_was_out_of_stock = !variant.in_stock_or_backorderable?
25
+ yield
26
+ if variant_was_out_of_stock && variant.in_stock_or_backorderable?
27
+ reload
28
+ variant.queue_webhooks_requests!('variant.back_in_stock')
29
+ end
30
+ end
31
+
32
+ def queue_webhooks_requests_for_product_back_in_stock!
33
+ product_was_out_of_stock = !product.any_variant_in_stock_or_backorderable?
34
+ yield
35
+ if product_was_out_of_stock && product.any_variant_in_stock_or_backorderable?
36
+ product.queue_webhooks_requests!('product.back_in_stock')
37
+ end
38
+ end
39
+
40
+ def queue_webhooks_requests_for_product_out_of_stock!
41
+ product_was_in_stock = product.any_variant_in_stock_or_backorderable?
42
+ yield
43
+ if product_was_in_stock && !product.any_variant_in_stock_or_backorderable?
44
+ product.queue_webhooks_requests!('product.out_of_stock')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ class Variant < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ after_update_commit :queue_webhooks_requests_for_variant_discontinued!
9
+ end
10
+
11
+ class_methods do
12
+ def custom_webhook_events
13
+ %w[variant.back_in_stock variant.backorderable variant.discontinued variant.out_of_stock]
14
+ end
15
+ end
16
+
17
+ def queue_webhooks_requests_for_variant_discontinued!
18
+ return unless discontinue_on_previously_changed?
19
+ return if (change = discontinue_on_previous_change).blank? || change.last.blank?
20
+
21
+ queue_webhooks_requests!('variant.discontinued')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -25,6 +25,10 @@ module Spree
25
25
 
26
26
  before_save :parse_subscriptions
27
27
 
28
+ def latest_event_at
29
+ events.order(:created_at).last&.created_at
30
+ end
31
+
28
32
  def self.with_urls_for(event)
29
33
  where(
30
34
  case ActiveRecord::Base.connection.adapter_name
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class AdminUserSerializer < UserSerializer
6
+ set_type :admin_user
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -5,6 +5,7 @@ module Spree
5
5
  class RefundSerializer < BaseSerializer
6
6
  include ResourceSerializerConcern
7
7
 
8
+ belongs_to :refunder, serializer: Dependencies.platform_admin_user_serializer.constantize
8
9
  belongs_to :payment
9
10
  belongs_to :reimbursement
10
11
  belongs_to :refund_reason, object_method_name: :reason
@@ -6,7 +6,7 @@ module Spree
6
6
  include ResourceSerializerConcern
7
7
 
8
8
  belongs_to :user
9
- belongs_to :created_by, serializer: :user, type: :user
9
+ belongs_to :created_by, serializer: Dependencies.platform_admin_user_serializer.constantize
10
10
  belongs_to :store_credit_category, object_method_name: :category, id_method_name: :category_id
11
11
  belongs_to :store_credit_type, object_method_name: :credit_type, id_method_name: :type_id
12
12
 
@@ -4,7 +4,7 @@ module Spree
4
4
  class CreditCardSerializer < BaseSerializer
5
5
  set_type :credit_card
6
6
 
7
- attributes :cc_type, :last_digits, :month, :year, :name, :default, :public_metadata
7
+ attributes :cc_type, :last_digits, :month, :year, :name, :default, :gateway_payment_profile_id, :public_metadata
8
8
 
9
9
  belongs_to :payment_method
10
10
  end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class ShippingCategorySerializer < BaseSerializer
5
+ set_type :shipping_category
6
+ attribute :name
7
+ end
8
+ end
9
+ end
10
+ end