spree_api 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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v2/caching.rb +14 -8
  3. data/app/controllers/concerns/spree/api/v2/storefront/order_concern.rb +2 -2
  4. data/app/controllers/spree/api/v2/base_controller.rb +7 -1
  5. data/app/controllers/spree/api/v2/storefront/products_controller.rb +8 -1
  6. data/app/controllers/spree/api/v2/storefront/variants_controller.rb +41 -0
  7. data/app/controllers/spree/api/v2/storefront/wishlists_controller.rb +43 -0
  8. data/app/models/concerns/spree/webhooks/has_webhooks.rb +13 -5
  9. data/app/models/spree/oauth_application.rb +7 -0
  10. data/app/models/spree/order/webhooks.rb +39 -0
  11. data/app/models/spree/payment/webhooks.rb +23 -0
  12. data/app/models/spree/product/webhooks.rb +42 -0
  13. data/app/models/spree/shipment/webhooks.rb +19 -0
  14. data/app/models/spree/stock_item/webhooks.rb +40 -0
  15. data/app/models/spree/stock_movement/webhooks.rb +49 -0
  16. data/app/models/spree/variant/webhooks.rb +25 -0
  17. data/app/models/spree/webhooks/subscriber.rb +4 -0
  18. data/app/serializers/spree/api/v2/platform/admin_user_serializer.rb +11 -0
  19. data/app/serializers/spree/api/v2/platform/refund_serializer.rb +1 -0
  20. data/app/serializers/spree/api/v2/platform/store_credit_serializer.rb +1 -1
  21. data/app/serializers/spree/v2/storefront/shipping_category_serializer.rb +10 -0
  22. data/app/serializers/spree/v2/storefront/shipping_method_serializer.rb +10 -0
  23. data/app/services/spree/api/error_handler.rb +1 -0
  24. data/app/services/spree/webhooks/subscribers/handle_request.rb +4 -4
  25. data/app/services/spree/webhooks/subscribers/make_request.rb +2 -2
  26. data/app/services/spree/webhooks/subscribers/queue_requests.rb +3 -3
  27. data/config/initializers/doorkeeper.rb +2 -0
  28. data/config/routes.rb +4 -1
  29. data/lib/spree/api/configuration.rb +3 -1
  30. data/lib/spree/api/dependencies.rb +5 -1
  31. data/lib/spree/api/engine.rb +0 -8
  32. data/lib/spree/api/testing_support/matchers/webhooks.rb +4 -4
  33. data/lib/spree/api/testing_support/spree_webhooks.rb +2 -2
  34. metadata +17 -13
  35. data/app/models/spree/api/webhooks/order_decorator.rb +0 -43
  36. data/app/models/spree/api/webhooks/payment_decorator.rb +0 -26
  37. data/app/models/spree/api/webhooks/product_decorator.rb +0 -46
  38. data/app/models/spree/api/webhooks/shipment_decorator.rb +0 -21
  39. data/app/models/spree/api/webhooks/stock_item_decorator.rb +0 -43
  40. data/app/models/spree/api/webhooks/stock_movement_decorator.rb +0 -52
  41. 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: 012d9b5279e5919cc152eba9678ca8682c1baca7b9256ff22c491db95d74de5a
4
+ data.tar.gz: 56e0fdb4db5ea020e7086b91fa3c401ea76527ea0b5408ca734e9a0abf72502b
5
5
  SHA512:
6
- metadata.gz: 414c7d4eeb5b12c336b63f8ce4f8a237a7bb38f372bef045926d201df72078bb0e17613496a6ef5e47c89554e95283f4d4f510f85e020f555a9b05763b3826e0
7
- data.tar.gz: 241adceec83317dda28aca5a2183c44d3906af1b197a3b0d41b0fd86fdd6ef88f7780135633b1e8da49824374752c28dcc9f354f5f05433c1ac7c2d01a3d3a67
6
+ metadata.gz: 8f9d2e9805651277ad363af26e8a97a10e89899336083e5b8f511d63bd99be127ab41b11c0c699fb42492c4d1f490a540273aaa6ec33ee9abcb221a36149b2e8
7
+ data.tar.gz: 8050de3c76e4d358a4d0198d87ea27e91ca2bc373ce8dfa61795207334b25e19751a7a1414bb17a3421c38037ae2ad51424e7a98506107c299f53acba8723ffa
@@ -5,22 +5,22 @@ 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)
8
+ # Calling reselect('*') is a workaround for the joined translations table
9
+ collection_cache_key = collection.unscope(:includes).unscope(:order).unscope(:group).reselect('*').cache_key_with_version
9
10
 
10
- ids = ids_and_timestamps.map(&:first)
11
- max_updated_at = ids_and_timestamps.map(&:last).max
11
+ params.delete(:page) if params[:page] == 1
12
12
 
13
13
  cache_key_parts = [
14
- self.class.to_s,
15
- max_updated_at,
16
- ids,
14
+ collection_cache_key,
17
15
  resource_includes,
18
16
  sparse_fields,
19
- serializer_params,
17
+ serializer_params_cache_key,
20
18
  params[:sort]&.strip,
21
19
  params[:page]&.to_s&.strip,
22
20
  params[:per_page]&.to_s&.strip,
23
- ].flatten.join('-')
21
+ ]
22
+ cache_key_parts += additional_cache_key_parts if defined?(additional_cache_key_parts)
23
+ cache_key_parts = cache_key_parts.flatten.join('-')
24
24
 
25
25
  Digest::MD5.hexdigest(cache_key_parts)
26
26
  end
@@ -31,6 +31,12 @@ module Spree
31
31
  expires_in: Spree::Api::Config[:api_v2_collection_cache_ttl],
32
32
  }
33
33
  end
34
+
35
+ def serializer_params_cache_key
36
+ serializer_params.values.map do |param|
37
+ param.try(:cache_key) || param.try(:flatten).try(:join, '-') || param.try(:to_s)
38
+ end.compact.join('-')
39
+ end
34
40
  end
35
41
  end
36
42
  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
 
@@ -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
 
@@ -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
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class ShippingMethodSerializer < BaseSerializer
5
+ set_type :shipping_method
6
+ attributes :name, :public_metadata
7
+ end
8
+ end
9
+ end
10
+ end
@@ -32,6 +32,7 @@ module Spree
32
32
  def report_error(exception:, message:, opts:)
33
33
  # overwrite this method in your application to support different error handlers
34
34
  # eg. Sentry, Rollbar, etc
35
+ Sentry.capture_exception(exception) if defined?(Sentry)
35
36
 
36
37
  success(exception: exception, message: message)
37
38
  end
@@ -19,7 +19,7 @@ module Spree
19
19
  end
20
20
  return process(:warn, msg("failed for '#{url}'")) if request.failed_request?
21
21
 
22
- process(:debug, msg("success for URL '#{url}'"))
22
+ process
23
23
  end
24
24
 
25
25
  private
@@ -30,8 +30,8 @@ module Spree
30
30
  delegate :id, :url, to: :subscriber
31
31
  delegate :created_at, :id, to: :event, prefix: true
32
32
 
33
- def process(log_level, msg)
34
- Rails.logger.public_send(log_level, msg)
33
+ def process(log_level = nil, msg = nil)
34
+ Rails.logger.public_send(log_level, msg) if msg.present? && log_level.present?
35
35
  make_request
36
36
  update_event(msg)
37
37
  nil
@@ -59,7 +59,7 @@ module Spree
59
59
  )
60
60
  end
61
61
 
62
- def update_event(msg)
62
+ def update_event(msg = nil)
63
63
  event.update(
64
64
  execution_time: execution_time,
65
65
  request_errors: msg,
@@ -45,7 +45,7 @@ module Spree
45
45
  def http
46
46
  http = Net::HTTP.new(uri_host, uri_port)
47
47
  http.read_timeout = webhooks_timeout.to_i if custom_read_timeout?
48
- http.use_ssl = true if use_ssl?
48
+ http.use_ssl = use_ssl?
49
49
  http
50
50
  end
51
51
 
@@ -71,7 +71,7 @@ module Spree
71
71
  end
72
72
 
73
73
  def use_ssl?
74
- !(Rails.env.development? || Rails.env.test?)
74
+ uri.scheme == 'https'
75
75
  end
76
76
 
77
77
  def uri
@@ -4,8 +4,8 @@ module Spree
4
4
  class QueueRequests
5
5
  prepend Spree::ServiceModule::Base
6
6
 
7
- def call(event_name:, webhook_payload_body:, **options)
8
- filtered_subscribers(event_name, webhook_payload_body, options).each do |subscriber|
7
+ def call(event_name:, webhook_payload_body:, record: nil, **options)
8
+ filtered_subscribers(event_name, webhook_payload_body, record, options).each do |subscriber|
9
9
  Spree::Webhooks::Subscribers::MakeRequestJob.perform_later(
10
10
  webhook_payload_body, event_name, subscriber
11
11
  )
@@ -14,7 +14,7 @@ module Spree
14
14
 
15
15
  private
16
16
 
17
- def filtered_subscribers(event_name, _, _)
17
+ def filtered_subscribers(event_name, webhook_payload_body, record, options)
18
18
  Spree::Webhooks::Subscriber.active.with_urls_for(event_name)
19
19
  end
20
20
  end
@@ -45,4 +45,6 @@ Doorkeeper.configure do
45
45
  # using Bcrupt for token secrets is currently not supported by Doorkeeper
46
46
  hash_token_secrets fallback: :plain
47
47
  hash_application_secrets fallback: :plain, using: '::Doorkeeper::SecretStoring::BCrypt'
48
+
49
+ access_token_expires_in 1.month
48
50
  end
data/config/routes.rb CHANGED
@@ -14,7 +14,7 @@ Spree::Core::Engine.add_routes do
14
14
  delete 'remove_line_item/:line_item_id', to: 'cart#remove_line_item', as: :cart_remove_line_item
15
15
  patch :set_quantity
16
16
  patch :apply_coupon_code
17
- delete 'remove_coupon_code/:coupon_code', to: 'cart#remove_coupon_code', as: :cart_remove_coupon_code
17
+ delete 'remove_coupon_code/:coupon_code', to: 'cart#remove_coupon_code', as: :cart_remove_coupon_code, constraints: { coupon_code: /[^\/]+/ }
18
18
  delete 'remove_coupon_code', to: 'cart#remove_coupon_code', as: :cart_remove_coupon_code_without_code
19
19
  get :estimate_shipping_rates
20
20
  patch :associate
@@ -45,6 +45,7 @@ Spree::Core::Engine.add_routes do
45
45
  get '/countries/:iso', to: 'countries#show', as: :country
46
46
  get '/order_status/:number', to: 'order_status#show', as: :order_status
47
47
  resources :products, only: %i[index show]
48
+ get '/products/:id/variants', to: 'variants#index', as: :product_variants
48
49
  resources :taxons, only: %i[index show], id: /.+/
49
50
  get '/stores/:code', to: 'stores#show', as: :store
50
51
  get '/store', to: 'stores#current', as: :current_store
@@ -59,6 +60,8 @@ Spree::Core::Engine.add_routes do
59
60
  post :add_item
60
61
  patch 'set_item_quantity/:item_id', to: 'wishlists#set_item_quantity', as: :set_item_quantity
61
62
  delete 'remove_item/:item_id', to: 'wishlists#remove_item', as: :remove_item
63
+ post :add_items
64
+ delete :remove_items
62
65
  end
63
66
  end
64
67
 
@@ -1,6 +1,8 @@
1
+ require 'spree/core/preferences/runtime_configuration'
2
+
1
3
  module Spree
2
4
  module Api
3
- class Configuration < Preferences::Configuration
5
+ class Configuration < Spree::Preferences::RuntimeConfiguration
4
6
  preference :api_v2_serializers_cache_ttl, :integer, default: 3600 # 1 hour in seconds
5
7
  preference :api_v2_collection_cache_ttl, :integer, default: 3600 # 1 hour in seconds
6
8
  preference :api_v2_collection_cache_namespace, :string, default: 'api_v2_collection_cache'
@@ -1,3 +1,5 @@
1
+ require 'spree/core/dependencies_helper'
2
+
1
3
  module Spree
2
4
  module Api
3
5
  class ApiDependencies
@@ -60,6 +62,7 @@ module Spree
60
62
  storefront_estimated_shipment_serializer: 'Spree::V2::Storefront::EstimatedShippingRateSerializer',
61
63
  storefront_store_serializer: 'Spree::V2::Storefront::StoreSerializer',
62
64
  storefront_order_serializer: 'Spree::V2::Storefront::OrderSerializer',
65
+ storefront_variant_serializer: 'Spree::V2::Storefront::VariantSerializer',
63
66
 
64
67
  # sorters
65
68
  storefront_collection_sorter: -> { Spree::Dependencies.collection_sorter },
@@ -81,11 +84,12 @@ module Spree
81
84
  storefront_find_by_variant_finder: -> { Spree::Dependencies.line_item_by_variant_finder },
82
85
  storefront_products_finder: -> { Spree::Dependencies.products_finder },
83
86
  storefront_taxon_finder: -> { Spree::Dependencies.taxon_finder },
87
+ storefront_variant_finder: -> { Spree::Dependencies.variant_finder },
84
88
 
85
89
  error_handler: 'Spree::Api::ErrorHandler',
86
90
 
87
91
  # serializers
88
- platform_admin_user_serializer: 'Spree::Api::V2::Platform::UserSerializer',
92
+ platform_admin_user_serializer: 'Spree::Api::V2::Platform::AdminUserSerializer',
89
93
 
90
94
  # coupon code handler
91
95
  platform_coupon_handler: -> { Spree::Dependencies.coupon_handler },
@@ -18,17 +18,9 @@ module Spree
18
18
  Migrations.new(config, engine_name).check
19
19
  end
20
20
 
21
- def self.activate
22
- Dir.glob(File.join(File.dirname(__FILE__), '../../../app/models/spree/api/webhooks/*_decorator*.rb')) do |c|
23
- Rails.application.config.cache_classes ? require(c) : load(c)
24
- end
25
- end
26
-
27
21
  def self.root
28
22
  @root ||= Pathname.new(File.expand_path('../../..', __dir__))
29
23
  end
30
-
31
- config.to_prepare &method(:activate).to_proc
32
24
  end
33
25
  end
34
26
  end
@@ -26,7 +26,7 @@
26
26
  # isn't added to the matcher definition, because it acts in a different
27
27
  # way depending on what's the resource being tested.
28
28
  #
29
- RSpec::Matchers.define :emit_webhook_event do |event_to_emit|
29
+ RSpec::Matchers.define :emit_webhook_event do |event_to_emit, record = nil|
30
30
  match do |block|
31
31
  queue_requests = instance_double(Spree::Webhooks::Subscribers::QueueRequests)
32
32
 
@@ -36,7 +36,7 @@ RSpec::Matchers.define :emit_webhook_event do |event_to_emit|
36
36
  with_webhooks_enabled { Timecop.freeze { block.call } }
37
37
 
38
38
  expect(queue_requests).to(
39
- have_received(:call).with(event_name: event_to_emit, webhook_payload_body: webhook_payload_body.to_json).once
39
+ have_received(:call).with(event_name: event_to_emit, webhook_payload_body: webhook_payload_body.to_json, record: record).once
40
40
  )
41
41
  end
42
42
 
@@ -61,7 +61,7 @@ RSpec::Matchers.define :emit_webhook_event do |event_to_emit|
61
61
  end
62
62
 
63
63
  def with_webhooks_enabled
64
- ENV['DISABLE_SPREE_WEBHOOKS'] = nil
64
+ Spree::Webhooks.disabled = false
65
65
  yield
66
- ENV['DISABLE_SPREE_WEBHOOKS'] = 'true'
66
+ Spree::Webhooks.disabled = true
67
67
  end
@@ -1,9 +1,9 @@
1
1
  RSpec.configure do |config|
2
2
  config.before(:each, :spree_webhooks) do
3
- ENV['DISABLE_SPREE_WEBHOOKS'] = nil
3
+ Spree::Webhooks.disabled = false
4
4
  end
5
5
 
6
6
  config.after(:each, :spree_webhooks) do
7
- ENV['DISABLE_SPREE_WEBHOOKS'] = 'true'
7
+ Spree::Webhooks.disabled = true
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.8.3
4
+ version: 4.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Bigg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n-tasks
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 4.8.3
117
+ version: 4.9.0
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 4.8.3
124
+ version: 4.9.0
125
125
  description: Spree's API
126
126
  email:
127
127
  - ryan@spreecommerce.com
@@ -203,6 +203,7 @@ files:
203
203
  - app/controllers/spree/api/v2/storefront/products_controller.rb
204
204
  - app/controllers/spree/api/v2/storefront/stores_controller.rb
205
205
  - app/controllers/spree/api/v2/storefront/taxons_controller.rb
206
+ - app/controllers/spree/api/v2/storefront/variants_controller.rb
206
207
  - app/controllers/spree/api/v2/storefront/wishlists_controller.rb
207
208
  - app/helpers/spree/api/v2/collection_options_helpers.rb
208
209
  - app/helpers/spree/api/v2/display_money_helper.rb
@@ -211,16 +212,16 @@ files:
211
212
  - app/models/concerns/spree/user_api_authentication.rb
212
213
  - app/models/concerns/spree/user_api_methods.rb
213
214
  - app/models/concerns/spree/webhooks/has_webhooks.rb
214
- - app/models/spree/api/webhooks/order_decorator.rb
215
- - app/models/spree/api/webhooks/payment_decorator.rb
216
- - app/models/spree/api/webhooks/product_decorator.rb
217
- - app/models/spree/api/webhooks/shipment_decorator.rb
218
- - app/models/spree/api/webhooks/stock_item_decorator.rb
219
- - app/models/spree/api/webhooks/stock_movement_decorator.rb
220
- - app/models/spree/api/webhooks/variant_decorator.rb
221
215
  - app/models/spree/oauth_access_grant.rb
222
216
  - app/models/spree/oauth_access_token.rb
223
217
  - app/models/spree/oauth_application.rb
218
+ - app/models/spree/order/webhooks.rb
219
+ - app/models/spree/payment/webhooks.rb
220
+ - app/models/spree/product/webhooks.rb
221
+ - app/models/spree/shipment/webhooks.rb
222
+ - app/models/spree/stock_item/webhooks.rb
223
+ - app/models/spree/stock_movement/webhooks.rb
224
+ - app/models/spree/variant/webhooks.rb
224
225
  - app/models/spree/webhooks/base.rb
225
226
  - app/models/spree/webhooks/event.rb
226
227
  - app/models/spree/webhooks/event_signature.rb
@@ -232,6 +233,7 @@ files:
232
233
  - app/serializers/spree/api/v2/base_serializer.rb
233
234
  - app/serializers/spree/api/v2/platform/address_serializer.rb
234
235
  - app/serializers/spree/api/v2/platform/adjustment_serializer.rb
236
+ - app/serializers/spree/api/v2/platform/admin_user_serializer.rb
235
237
  - app/serializers/spree/api/v2/platform/asset_serializer.rb
236
238
  - app/serializers/spree/api/v2/platform/base_serializer.rb
237
239
  - app/serializers/spree/api/v2/platform/calculator_serializer.rb
@@ -337,6 +339,8 @@ files:
337
339
  - app/serializers/spree/v2/storefront/product_serializer.rb
338
340
  - app/serializers/spree/v2/storefront/promotion_serializer.rb
339
341
  - app/serializers/spree/v2/storefront/shipment_serializer.rb
342
+ - app/serializers/spree/v2/storefront/shipping_category_serializer.rb
343
+ - app/serializers/spree/v2/storefront/shipping_method_serializer.rb
340
344
  - app/serializers/spree/v2/storefront/shipping_rate_serializer.rb
341
345
  - app/serializers/spree/v2/storefront/state_serializer.rb
342
346
  - app/serializers/spree/v2/storefront/stock_location_serializer.rb
@@ -402,9 +406,9 @@ licenses:
402
406
  - BSD-3-Clause
403
407
  metadata:
404
408
  bug_tracker_uri: https://github.com/spree/spree/issues
405
- changelog_uri: https://github.com/spree/spree/releases/tag/v4.8.3
409
+ changelog_uri: https://github.com/spree/spree/releases/tag/v4.9.0
406
410
  documentation_uri: https://docs.spreecommerce.org/
407
- source_code_uri: https://github.com/spree/spree/tree/v4.8.3
411
+ source_code_uri: https://github.com/spree/spree/tree/v4.9.0
408
412
  post_install_message:
409
413
  rdoc_options: []
410
414
  require_paths:
@@ -1,43 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module OrderDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[order.canceled order.placed order.resumed order.shipped]
8
- end
9
-
10
- base.after_update_commit :queue_webhooks_requests_for_order_resumed!
11
- end
12
-
13
- def after_cancel
14
- super
15
- queue_webhooks_requests!('order.canceled')
16
- end
17
-
18
- def finalize!
19
- super
20
- queue_webhooks_requests!('order.placed')
21
- end
22
-
23
- def after_resume
24
- super
25
- queue_webhooks_requests!('order.resumed')
26
- self.state_machine_resumed = false
27
- end
28
-
29
- private
30
-
31
- def queue_webhooks_requests_for_order_resumed!
32
- return if state_machine_resumed?
33
- return unless state_previously_changed?
34
- return unless state_previous_change&.last == 'resumed'
35
-
36
- queue_webhooks_requests!('order.resumed')
37
- end
38
- end
39
- end
40
- end
41
- end
42
-
43
- Spree::Order.prepend(Spree::Api::Webhooks::OrderDecorator)
@@ -1,26 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module PaymentDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[payment.paid payment.voided]
8
- end
9
- end
10
-
11
- def after_void
12
- super
13
- queue_webhooks_requests!('payment.voided')
14
- end
15
-
16
- def after_completed
17
- super
18
- queue_webhooks_requests!('payment.paid')
19
- order.queue_webhooks_requests!('order.paid') if order.paid?
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Spree::Payment.prepend(Spree::Api::Webhooks::PaymentDecorator)
@@ -1,46 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module ProductDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[product.back_in_stock product.backorderable product.discontinued
8
- product.out_of_stock product.activated product.archived product.drafted]
9
- end
10
-
11
- def base.ignored_attributes_for_update_webhook_event
12
- %w[status]
13
- end
14
-
15
- base.after_update_commit :queue_webhooks_requests_for_product_discontinued!
16
- end
17
-
18
- def after_activate
19
- super
20
- queue_webhooks_requests!('product.activated')
21
- end
22
-
23
- def after_archive
24
- super
25
- queue_webhooks_requests!('product.archived')
26
- end
27
-
28
- def after_draft
29
- super
30
- queue_webhooks_requests!('product.drafted')
31
- end
32
-
33
- private
34
-
35
- def queue_webhooks_requests_for_product_discontinued!
36
- return unless discontinue_on_previously_changed?
37
- return if (change = discontinue_on_previous_change).blank? || change.last.blank?
38
-
39
- queue_webhooks_requests!('product.discontinued')
40
- end
41
- end
42
- end
43
- end
44
- end
45
-
46
- Spree::Product.prepend(Spree::Api::Webhooks::ProductDecorator)
@@ -1,21 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module ShipmentDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[shipment.shipped]
8
- end
9
- end
10
-
11
- def after_ship
12
- super
13
- queue_webhooks_requests!('shipment.shipped')
14
- order.queue_webhooks_requests!('order.shipped') if order.fully_shipped?
15
- end
16
- end
17
- end
18
- end
19
- end
20
-
21
- Spree::Shipment.prepend(Spree::Api::Webhooks::ShipmentDecorator)
@@ -1,43 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module StockItemDecorator
5
- def self.prepended(base)
6
- base.around_save :queue_webhooks_requests_for_variant_backorderable!
7
- base.around_save :queue_webhooks_requests_for_product_backorderable!
8
- end
9
-
10
- private
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
41
- end
42
-
43
- Spree::StockItem.prepend(Spree::Api::Webhooks::StockItemDecorator)
@@ -1,52 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module StockMovementDecorator
5
- def self.prepended(base)
6
- base.around_save :queue_webhooks_requests_for_variant_out_of_stock!
7
- base.around_save :queue_webhooks_requests_for_variant_back_in_stock!
8
- base.around_save :queue_webhooks_requests_for_product_out_of_stock!
9
- base.around_save :queue_webhooks_requests_for_product_back_in_stock!
10
- end
11
-
12
- private
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
50
- end
51
-
52
- Spree::StockMovement.prepend(Spree::Api::Webhooks::StockMovementDecorator)
@@ -1,26 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module VariantDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[variant.back_in_stock variant.backorderable variant.discontinued variant.out_of_stock]
8
- end
9
-
10
- base.after_update_commit :queue_webhooks_requests_for_variant_discontinued!
11
- end
12
-
13
- private
14
-
15
- def queue_webhooks_requests_for_variant_discontinued!
16
- return unless discontinue_on_previously_changed?
17
- return if (change = discontinue_on_previous_change).blank? || change.last.blank?
18
-
19
- queue_webhooks_requests!('variant.discontinued')
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Spree::Variant.prepend(Spree::Api::Webhooks::VariantDecorator)